From 13a74133f512d61a80d149393edfdcdecbcea3ef Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:37:50 +0100 Subject: [PATCH] feat: Pay Anything from Any Wallet (#2450) * feat: Pay Anything from Any Wallet * feat: Trigger payment flow when address is pasted * chore: Remove unneeded changes * feat Expand address checks and fix scanning bch qrs * fix: Add support for dogecoin * feat: More modifications for Pay from Any Wallet This change: - Adds the pay from any wallet flow to externally signed QR codes - Adds fixes for review comments - Fixes failing tests --------- Co-authored-by: Omar Hatem --- cw_core/lib/crypto_currency.dart | 22 ++ lib/core/universal_address_detector.dart | 232 +++++++++++++++++ lib/di.dart | 15 ++ lib/src/screens/send/send_page.dart | 8 + lib/src/screens/send/widgets/send_card.dart | 198 ++++++++++++-- .../payment_confirmation_bottom_sheet.dart | 161 ++++++++++++ .../wallet_switcher_bottom_sheet.dart | 130 ++++++++++ .../payment/payment_view_model.dart | 124 +++++++++ lib/view_model/send/send_view_model.dart | 2 + .../wallet_switcher_view_model.dart | 63 +++++ res/values/strings_ar.arb | 2 + res/values/strings_bg.arb | 2 + res/values/strings_cs.arb | 2 + res/values/strings_de.arb | 2 + res/values/strings_en.arb | 2 + res/values/strings_es.arb | 2 + res/values/strings_fr.arb | 4 +- res/values/strings_ha.arb | 2 + res/values/strings_hi.arb | 2 + res/values/strings_hr.arb | 2 + res/values/strings_hy.arb | 2 + res/values/strings_id.arb | 2 + res/values/strings_it.arb | 2 + res/values/strings_ja.arb | 2 + res/values/strings_ko.arb | 2 + res/values/strings_my.arb | 2 + res/values/strings_nl.arb | 2 + res/values/strings_pl.arb | 2 + res/values/strings_pt.arb | 2 + res/values/strings_ru.arb | 2 + res/values/strings_th.arb | 2 + res/values/strings_tl.arb | 2 + res/values/strings_tr.arb | 2 + res/values/strings_uk.arb | 2 + res/values/strings_ur.arb | 2 + res/values/strings_vi.arb | 2 + res/values/strings_yo.arb | 2 + res/values/strings_zh.arb | 2 + .../core/universal_address_detector_test.dart | 242 ++++++++++++++++++ 39 files changed, 1228 insertions(+), 27 deletions(-) create mode 100644 lib/core/universal_address_detector.dart create mode 100644 lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart create mode 100644 lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart create mode 100644 lib/view_model/payment/payment_view_model.dart create mode 100644 lib/view_model/wallet_switcher_view_model.dart create mode 100644 test/core/universal_address_detector_test.dart diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 1e95fa958..2f90e2cb3 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -261,6 +261,24 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen return acc; }); + // Scheme to currency mapping for URI scheme + static final Map _schemeCurrencyMap = { + 'bitcoin': btc, + 'bitcoincash': bch, + 'polygon': maticpoly, + 'nano-gpt': nano, + 'secret': scrt, + 'stellar': xlm, + 'avalanche': avaxc, + 'binance': bnb, + 'tether': usdt, + 'usdcoin': usdc, + 'wrappedbitcoin': wbtc, + 'wrappedether': weth, + 'shibainu': shib, + 'zcash': zec, + }; + static CryptoCurrency deserialize({required int raw}) { if (CryptoCurrency._rawCurrencyMap[raw] == null) { final s = 'Unexpected token: $raw for CryptoCurrency deserialize'; @@ -271,6 +289,10 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen // TODO: refactor this static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) { + + final schemeMatch = _schemeCurrencyMap[name.toLowerCase()]; + if (schemeMatch != null) return schemeMatch; + try { return CryptoCurrency.all.firstWhere((element) => element.title.toLowerCase() == name.toLowerCase() && diff --git a/lib/core/universal_address_detector.dart b/lib/core/universal_address_detector.dart new file mode 100644 index 000000000..88bdbafff --- /dev/null +++ b/lib/core/universal_address_detector.dart @@ -0,0 +1,232 @@ +import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/wallet_type.dart'; + +class AddressDetectionResult { + AddressDetectionResult({ + required this.address, + this.detectedCurrency, + this.detectedWalletType, + this.amount, + this.note, + this.scheme, + this.pjUri, + this.callbackUrl, + this.callbackMessage, + required this.isValid, + this.errorMessage, + }); + + final String address; + final CryptoCurrency? detectedCurrency; + final WalletType? detectedWalletType; + final String? amount; + final String? note; + final String? scheme; + final String? pjUri; + final String? callbackUrl; + final String? callbackMessage; + final bool isValid; + final String? errorMessage; +} + +/// Universal address detector that can identify cryptocurrency addresses from various formats +class UniversalAddressDetector { + /// Detects cryptocurrency address from various input formats + /// Supports: raw addresses, URIs, and QR codes + static AddressDetectionResult detectAddress(String input) { + if (input.trim().isEmpty) { + return AddressDetectionResult( + address: '', + detectedCurrency: null, + isValid: false, + errorMessage: 'Empty input provided', + ); + } + + // Try to parse as URI first + final uriResult = _detectFromUri(input); + if (uriResult.isValid) return uriResult; + + // Try to detect from raw address patterns + final rawAddressResult = _detectFromRawAddress(input); + if (rawAddressResult.isValid) return rawAddressResult; + + return AddressDetectionResult( + address: input, + detectedCurrency: null, + isValid: false, + errorMessage: 'Unable to detect valid cryptocurrency address', + ); + } + + /// Detects address from URI format (e.g., bitcoin:address?amount=0.001) + static AddressDetectionResult _detectFromUri(String input) { + try { + final uri = Uri.parse(input); + + final paymentRequest = PaymentRequest.fromUri(uri); + + // Determine currency from scheme + final currency = CryptoCurrency.fromString(uri.scheme.toLowerCase()); + + return AddressDetectionResult( + address: paymentRequest.address, + detectedCurrency: currency, + detectedWalletType: cryptoCurrencyToWalletType(currency), + amount: paymentRequest.amount, + note: paymentRequest.note, + scheme: paymentRequest.scheme, + pjUri: paymentRequest.pjUri, + callbackUrl: paymentRequest.callbackUrl, + callbackMessage: paymentRequest.callbackMessage, + isValid: true, + ); + } catch (e) { + return AddressDetectionResult( + address: input, + detectedCurrency: null, + isValid: false, + errorMessage: 'Failed to parse URI: $e', + ); + } + } + + /// Detect address from raw address patterns + static AddressDetectionResult _detectFromRawAddress(String input) { + final cleanInput = input.trim(); + + // Detection patterns ordered by specificity (most specific first) + final detectionPatterns = [ + // Lightning Network + _DetectionPattern( + pattern: RegExp(r'^(lnbc|LNBC)[a-km-zA-HJ-NP-Z1-9]{1,}[a-zA-Z0-9]+$'), + currency: CryptoCurrency.btcln, + ), + + // Bitcoin Bech32 + _DetectionPattern( + pattern: RegExp(r'^bc1[a-km-zA-HJ-NP-Z1-9]{25,39}$'), + currency: CryptoCurrency.btc, + ), + + // Litecoin Bech32 + _DetectionPattern( + pattern: RegExp(r'^ltc1[a-z0-9]{25,50}$'), + currency: CryptoCurrency.ltc, + ), + + // Bitcoin Cash + _DetectionPattern( + pattern: RegExp(r'^(q|p)[a-z0-9]{41}$'), + currency: CryptoCurrency.bch, + ), + + // Ethereum + _DetectionPattern( + pattern: RegExp(r'^0x[a-fA-F0-9]{40}$'), + currency: CryptoCurrency.eth, + ), + + // Nano + _DetectionPattern( + pattern: RegExp(r'^nano_[a-km-zA-HJ-NP-Z1-9]{60}$'), + currency: CryptoCurrency.nano, + ), + + // Banano + _DetectionPattern( + pattern: RegExp(r'^ban_[a-km-zA-HJ-NP-Z1-9]{60}$'), + currency: CryptoCurrency.banano, + ), + + // Tron + _DetectionPattern( + pattern: RegExp(r'^T[a-km-zA-HJ-NP-Z1-9]{33}$'), + currency: CryptoCurrency.trx, + ), + + // Zano alias + _DetectionPattern( + pattern: RegExp(r'^@[\w\d.-]+$'), + currency: CryptoCurrency.zano, + ), + + // Monero (standard) - 95 characters + _DetectionPattern( + pattern: RegExp(r'^4[a-km-zA-HJ-NP-Z1-9]{94}$'), + currency: CryptoCurrency.xmr, + ), + + // Monero (integrated) - 95 characters + _DetectionPattern( + pattern: RegExp(r'^8[a-km-zA-HJ-NP-Z1-9]{94}$'), + currency: CryptoCurrency.xmr, + ), + + // Wownero - 97 characters + _DetectionPattern( + pattern: RegExp(r'^W[a-km-zA-HJ-NP-Z1-9]{96}$'), + currency: CryptoCurrency.wow, + ), + + // Zano (long addresses) - 97 characters + _DetectionPattern( + pattern: RegExp(r'^Z[a-km-zA-HJ-NP-Z1-9]{96}$'), + currency: CryptoCurrency.zano, + ), + + // Decred + _DetectionPattern( + pattern: RegExp(r'^(D|T|S)[ksecS][a-km-zA-HJ-NP-Z1-9]+$'), + currency: CryptoCurrency.dcr, + ), + + // Bitcoin P2PKH/P2SH (legacy formats) + _DetectionPattern( + pattern: RegExp(r'^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$'), + currency: CryptoCurrency.btc, + ), + + // Litecoin P2PKH/P2SH + _DetectionPattern( + pattern: RegExp(r'^(L|M|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$'), + currency: CryptoCurrency.ltc, + ), + + // Solana (Base58 format, 32-44 chars) + _DetectionPattern( + pattern: RegExp(r'^[1-9A-HJ-NP-Za-km-z]{32,44}$'), + currency: CryptoCurrency.sol, + ), + ]; + + // Test each pattern in order of specificity + for (final pattern in detectionPatterns) { + if (pattern.pattern.hasMatch(cleanInput)) { + return AddressDetectionResult( + address: cleanInput, + detectedCurrency: pattern.currency, + detectedWalletType: cryptoCurrencyToWalletType(pattern.currency), + isValid: true, + ); + } + } + + return AddressDetectionResult( + address: cleanInput, + detectedCurrency: null, + isValid: false, + ); + } +} + +class _DetectionPattern { + const _DetectionPattern({ + required this.pattern, + required this.currency, + }); + + final RegExp pattern; + final CryptoCurrency currency; +} diff --git a/lib/di.dart b/lib/di.dart index 84047fa71..ada849917 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -282,6 +282,8 @@ import 'package:cake_wallet/core/trade_monitor.dart'; import 'package:cake_wallet/core/reset_service.dart'; import 'package:cake_wallet/view_model/dev/socket_health_logs_view_model.dart'; import 'package:cake_wallet/src/screens/dev/socket_health_logs_page.dart'; +import 'package:cake_wallet/view_model/payment/payment_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart'; final getIt = GetIt.instance; @@ -816,6 +818,8 @@ Future setup({ sendViewModel: getIt.get(param1: coinTypeToSpendFrom), authService: getIt.get(), initialPaymentRequest: initialPaymentRequest, + paymentViewModel: getIt.get(), + walletSwitcherViewModel: getIt.get(), )); getIt.registerFactory( @@ -1126,6 +1130,17 @@ Future setup({ getIt.registerFactory(() => ExchangeTemplatePage(getIt.get())); + getIt.registerFactory(() => PaymentViewModel( + appStore: getIt.get(), + walletInfoSource: _walletInfoSource, + )); + + getIt.registerFactory(() => WalletSwitcherViewModel( + appStore: getIt.get(), + walletLoadingService: getIt.get(), + walletInfoSource: _walletInfoSource, + )); + getIt.registerFactoryParam((WalletType param1, __) { switch (param1) { case WalletType.monero: diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 96babfa7c..9bc18dc2e 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -31,7 +31,9 @@ import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/request_review_handler.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/payment/payment_view_model.dart'; import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart'; @@ -48,10 +50,14 @@ class SendPage extends BasePage { SendPage({ required this.sendViewModel, required this.authService, + required this.paymentViewModel, + required this.walletSwitcherViewModel, this.initialPaymentRequest, }) : _formKey = GlobalKey(); final SendViewModel sendViewModel; + final PaymentViewModel paymentViewModel; + final WalletSwitcherViewModel walletSwitcherViewModel; final AuthService authService; final GlobalKey _formKey; final controller = PageController(initialPage: 0); @@ -179,6 +185,8 @@ class SendPage extends BasePage { key: output.key, output: output, sendViewModel: sendViewModel, + paymentViewModel: paymentViewModel, + walletSwitcherViewModel: walletSwitcherViewModel, initialPaymentRequest: initialPaymentRequest, cryptoAmountFocus: cryptoAmountFocus, fiatAmountFocus: fiatAmountFocus, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 7260b8d00..32046bfae 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,6 +1,8 @@ import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; +import 'package:cake_wallet/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart'; +import 'package:cake_wallet/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; @@ -8,12 +10,16 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/themes/core/material_base_theme.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/view_model/payment/payment_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coin_type.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -29,6 +35,8 @@ class SendCard extends StatefulWidget { Key? key, required this.output, required this.sendViewModel, + required this.paymentViewModel, + required this.walletSwitcherViewModel, required this.currentTheme, this.initialPaymentRequest, this.cryptoAmountFocus, @@ -37,6 +45,8 @@ class SendCard extends StatefulWidget { final Output output; final SendViewModel sendViewModel; + final PaymentViewModel paymentViewModel; + final WalletSwitcherViewModel walletSwitcherViewModel; final PaymentRequest? initialPaymentRequest; final FocusNode? cryptoAmountFocus; final FocusNode? fiatAmountFocus; @@ -44,14 +54,16 @@ class SendCard extends StatefulWidget { @override SendCardState createState() => SendCardState( - output: output, - sendViewModel: sendViewModel, - initialPaymentRequest: initialPaymentRequest, - currentTheme: currentTheme - // cryptoAmountFocus: cryptoAmountFocus ?? FocusNode(), - // fiatAmountFocus: fiatAmountFocus ?? FocusNode(), - // cryptoAmountFocus: FocusNode(), - // fiatAmountFocus: FocusNode(), + output: output, + sendViewModel: sendViewModel, + paymentViewModel: paymentViewModel, + walletSwitcherViewModel: walletSwitcherViewModel, + initialPaymentRequest: initialPaymentRequest, + currentTheme: currentTheme, + // cryptoAmountFocus: cryptoAmountFocus ?? FocusNode(), + // fiatAmountFocus: fiatAmountFocus ?? FocusNode(), + // cryptoAmountFocus: FocusNode(), + // fiatAmountFocus: FocusNode(), ); } @@ -59,6 +71,8 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).error, - alertContent: S.of(context).unmatched_currencies, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }, - ); + final separator = initialPaymentRequest!.scheme.isNotEmpty ? ":" : ""; + final uri = initialPaymentRequest!.scheme + separator + initialPaymentRequest!.address; + _handlePaymentFlow(uri, initialPaymentRequest!); } }, ); } } + @override + void dispose() { + addressController.dispose(); + cryptoAmountController.dispose(); + fiatAmountController.dispose(); + noteController.dispose(); + extractedAddressController.dispose(); + addressFocusNode.dispose(); + super.dispose(); + } + + Future _handlePaymentFlow(String uri, PaymentRequest paymentRequest) async { + try { + final result = await paymentViewModel.processAddress(uri); + + switch (result.type) { + case PaymentFlowType.singleWallet: + case PaymentFlowType.multipleWallets: + case PaymentFlowType.noWallets: + await _showPaymentConfirmation( + paymentViewModel, + walletSwitcherViewModel, + paymentRequest, + result, + ); + break; + case PaymentFlowType.currentWalletCompatible: + case PaymentFlowType.error: + case PaymentFlowType.incompatible: + _applyPaymentRequest(paymentRequest); + break; + } + } catch (e) { + printV('Payment flow error: $e'); + _applyPaymentRequest(paymentRequest); + } + } + + Future _showPaymentConfirmation( + PaymentViewModel paymentViewModel, + WalletSwitcherViewModel walletSwitcherViewModel, + PaymentRequest paymentRequest, + PaymentFlowResult result, + ) async { + await showModalBottomSheet( + context: context, + isDismissible: true, + isScrollControlled: true, + builder: (BuildContext context) { + return PaymentConfirmationBottomSheet( + paymentFlowResult: result, + paymentViewModel: paymentViewModel, + walletSwitcherViewModel: walletSwitcherViewModel, + currentTheme: currentTheme, + paymentRequest: paymentRequest, + onSelectWallet: () => _handleSelectWallet( + paymentViewModel, + walletSwitcherViewModel, + paymentRequest, + ), + onChangeWallet: () => _handleChangeWallet( + paymentViewModel, + walletSwitcherViewModel, + paymentRequest, + result, + ), + onSwap: () => Navigator.of(context).pushNamed(Routes.exchange, arguments: paymentRequest), + ); + }, + ); + } + + Future _handleSelectWallet( + PaymentViewModel paymentViewModel, + WalletSwitcherViewModel walletSwitcherViewModel, + PaymentRequest paymentRequest, + ) async { + Navigator.of(context).pop(); + + await showModalBottomSheet( + context: context, + isDismissible: true, + isScrollControlled: true, + builder: (BuildContext dialogContext) { + return WalletSwitcherBottomSheet( + viewModel: walletSwitcherViewModel, + currentTheme: currentTheme, + filterWalletType: paymentViewModel.detectedWalletType, + ); + }, + ); + + final success = await walletSwitcherViewModel.switchToSelectedWallet(); + + if (success) { + _applyPaymentRequest(paymentRequest); + } + } + + Future _handleChangeWallet( + PaymentViewModel paymentViewModel, + WalletSwitcherViewModel walletSwitcherViewModel, + PaymentRequest paymentRequest, + PaymentFlowResult result, + ) async { + Navigator.of(context).pop(); + + if (result.wallet != null) { + walletSwitcherViewModel.selectWallet(result.wallet!); + final success = await walletSwitcherViewModel.switchToSelectedWallet(); + if (success) { + _applyPaymentRequest(paymentRequest); + } + } + } + + /// Apply payment request to current form + void _applyPaymentRequest(PaymentRequest paymentRequest) { + if (sendViewModel.usePayjoin) { + sendViewModel.payjoinUri = paymentRequest.pjUri; + } + addressController.text = paymentRequest.address; + cryptoAmountController.text = paymentRequest.amount; + noteController.text = paymentRequest.note; + } + @override Widget build(BuildContext context) { super.build(context); @@ -185,13 +319,11 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin Navigator.of(context).pop(), + text: S.current.cancel, + color: Theme.of(context).colorScheme.surfaceContainer, + textColor: Theme.of(context).colorScheme.onSecondaryContainer, + ), + const SizedBox(height: 10), + PrimaryButton( + onPressed: onSwap, + text: '${S.current.swap} $currentWalletName', + color: hasAtLeastOneWallet + ? Theme.of(context).colorScheme.surfaceContainer + : Theme.of(context).colorScheme.primary, + textColor: hasAtLeastOneWallet + ? Theme.of(context).colorScheme.onSecondaryContainer + : Theme.of(context).colorScheme.onPrimary, + ), + const SizedBox(height: 32), + ], + ], + ), + ); + }, + ); + } +} diff --git a/lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart new file mode 100644 index 000000000..81e0fd075 --- /dev/null +++ b/lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart @@ -0,0 +1,130 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/themes/core/material_base_theme.dart'; + +class WalletSwitcherBottomSheet extends BaseBottomSheet { + WalletSwitcherBottomSheet({ + Key? key, + required this.viewModel, + required this.currentTheme, + this.filterWalletType, + this.subtitle, + this.onWalletSelected, + }) : super( + titleText: S.current.select_a_wallet, + footerType: FooterType.none, + maxHeight: 900, + currentTheme: currentTheme, + ); + + final WalletSwitcherViewModel viewModel; + final MaterialThemeBase currentTheme; + final WalletType? filterWalletType; + final String? subtitle; + final Function(WalletInfo)? onWalletSelected; + + @override + Widget contentWidget(BuildContext context) { + return _WalletSwitcherContent( + viewModel: viewModel, + filterWalletType: filterWalletType, + subtitle: subtitle, + onWalletSelected: onWalletSelected, + ); + } +} + +class _WalletSwitcherContent extends StatelessWidget { + const _WalletSwitcherContent({ + required this.viewModel, + this.filterWalletType, + this.subtitle, + this.onWalletSelected, + }); + + final WalletSwitcherViewModel viewModel; + final WalletType? filterWalletType; + final String? subtitle; + final Function(WalletInfo)? onWalletSelected; + + @override + Widget build(BuildContext context) { + return Observer( + builder: (_) { + final wallets = viewModel.getWallets(filterWalletType); + + if (viewModel.isProcessing) { + return Container( + height: 200, + child: Center( + child: CircularProgressIndicator( + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + } + + return Container( + height: 400, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: StandardList( + itemCount: wallets.length, + itemBuilder: (context, index) { + final wallet = wallets[index]; + + return InkWell( + onTap: () { + viewModel.selectWallet(wallet); + Navigator.of(context).pop(); + }, + borderRadius: BorderRadius.circular(12), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(16), + ), + height: 60, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + children: [ + Image.asset( + walletTypeToCryptoCurrency(wallet.type).iconPath!, + width: 32, + height: 32, + ), + const SizedBox(width: 16), + Text( + wallet.name, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 18, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onSurface, + letterSpacing: 0.0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/view_model/payment/payment_view_model.dart b/lib/view_model/payment/payment_view_model.dart new file mode 100644 index 000000000..21f505f8a --- /dev/null +++ b/lib/view_model/payment/payment_view_model.dart @@ -0,0 +1,124 @@ +import 'dart:async'; + +import 'package:cake_wallet/core/universal_address_detector.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; + +part 'payment_view_model.g.dart'; + +class PaymentViewModel = PaymentViewModelBase with _$PaymentViewModel; + +abstract class PaymentViewModelBase with Store { + PaymentViewModelBase({ + required this.appStore, + required this.walletInfoSource, + }); + + final AppStore appStore; + final Box walletInfoSource; + + @observable + WalletType? detectedWalletType; + + @observable + bool isProcessing = false; + + @computed + WalletType get currentWalletType => appStore.wallet!.type; + + /// Main entry point - detect address type and check compatibility + @action + Future processAddress(String addressData) async { + try { + detectedWalletType = null; + isProcessing = true; + + // Detect address type + final detectionResult = UniversalAddressDetector.detectAddress(addressData); + + if (!detectionResult.isValid || detectionResult.detectedWalletType == null) { + return PaymentFlowResult.incompatible('Unable to detect address type'); + } + + detectedWalletType = detectionResult.detectedWalletType!; + + // Check if current wallet is compatible + final currentWallet = appStore.wallet; + if (currentWallet != null && currentWallet.type == detectedWalletType) { + return PaymentFlowResult.currentWalletCompatible(); + } + + final compatibleWallets = getWalletsByType(detectedWalletType!); + + switch (compatibleWallets.length) { + case 0: + return PaymentFlowResult.noWallets(detectedWalletType!); + case 1: + return PaymentFlowResult.singleWallet(compatibleWallets.first); + default: + return PaymentFlowResult.multipleWallets(compatibleWallets); + } + } catch (e) { + printV('PaymentViewModel error: $e'); + return PaymentFlowResult.error('Payment processing failed: $e'); + } finally { + isProcessing = false; + } + } + + List getWalletsByType(WalletType walletType) { + return walletInfoSource.values.where((wallet) => wallet.type == walletType).toList(); + } +} + +class PaymentFlowResult { + final PaymentFlowType type; + final String? message; + final WalletInfo? wallet; + final List wallets; + final WalletType? walletType; + + PaymentFlowResult._({ + required this.type, + this.message, + this.wallet, + this.wallets = const [], + this.walletType, + }); + + /// Current wallet is compatible + factory PaymentFlowResult.currentWalletCompatible() => PaymentFlowResult._(type: PaymentFlowType.currentWalletCompatible); + + /// Single compatible wallet available + factory PaymentFlowResult.singleWallet(WalletInfo wallet) => + PaymentFlowResult._(type: PaymentFlowType.singleWallet, wallet: wallet); + + /// Multiple compatible wallets available + factory PaymentFlowResult.multipleWallets(List wallets) => + PaymentFlowResult._(type: PaymentFlowType.multipleWallets, wallets: wallets); + + /// No compatible wallets available + factory PaymentFlowResult.noWallets(WalletType walletType) => + PaymentFlowResult._(type: PaymentFlowType.noWallets, walletType: walletType); + + /// Error occurred + factory PaymentFlowResult.error(String message) => + PaymentFlowResult._(type: PaymentFlowType.error, message: message); + + /// Incompatible address + factory PaymentFlowResult.incompatible(String message) => + PaymentFlowResult._(type: PaymentFlowType.incompatible, message: message); +} + +enum PaymentFlowType { + currentWalletCompatible, + singleWallet, + multipleWallets, + noWallets, + error, + incompatible, +} diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 3be2b30db..a43fbbf58 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -37,7 +37,9 @@ import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cake_wallet/view_model/payment/payment_view_model.dart'; import 'package:cake_wallet/view_model/send/fees_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; diff --git a/lib/view_model/wallet_switcher_view_model.dart b/lib/view_model/wallet_switcher_view_model.dart new file mode 100644 index 000000000..930d58ef3 --- /dev/null +++ b/lib/view_model/wallet_switcher_view_model.dart @@ -0,0 +1,63 @@ +import 'dart:async'; + +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/core/wallet_loading_service.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; + +part 'wallet_switcher_view_model.g.dart'; + +class WalletSwitcherViewModel = WalletSwitcherViewModelBase with _$WalletSwitcherViewModel; + +abstract class WalletSwitcherViewModelBase with Store { + WalletSwitcherViewModelBase({ + required this.appStore, + required this.walletLoadingService, + required this.walletInfoSource, + }); + + final AppStore appStore; + final WalletLoadingService walletLoadingService; + final Box walletInfoSource; + + @observable + WalletInfo? selectedWallet; + + @observable + bool isProcessing = false; + + @action + List getWallets(WalletType? walletType) { + if (walletType == null) { + return walletInfoSource.values.toList(); + } + + return walletInfoSource.values.where((wallet) => wallet.type == walletType).toList(); + } + + @action + void selectWallet(WalletInfo walletInfo) => selectedWallet = walletInfo; + + @action + Future switchToSelectedWallet() async { + if (selectedWallet == null) return false; + + try { + isProcessing = true; + + final wallet = await walletLoadingService.load(selectedWallet!.type, selectedWallet!.name); + + await appStore.changeCurrentWallet(wallet); + + return true; + } catch (e) { + printV('Failed to switch wallet: $e'); + return false; + } finally { + isProcessing = false; + } + } +} diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 1f913cbe1..497136f5f 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -774,6 +774,7 @@ "seedtype": "البذور", "seedtype_alert_content": "مشاركة البذور مع محافظ أخرى ممكن فقط مع BIP39 Seedtype.", "seedtype_alert_title": "تنبيه البذور", + "select_a_wallet": "حدد محفظة", "select_backup_file": "حدد ملف النسخ الاحتياطي", "select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.", "select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ", @@ -906,6 +907,7 @@ "swap": "تبديل", "sweeping_wallet": "كنس المحفظة", "sweeping_wallet_alert": "لن يستغرق هذا وقتًا طويلاً. لا تترك هذه الشاشة وإلا فقد يتم فقد أموال سويبت", + "switch_wallet": "تبديل المحفظة", "switchToETHWallet": "ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ Ethereum ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ", "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ", "symbol": "ﺰﻣﺭ", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 95535945d..86fb3550f 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -774,6 +774,7 @@ "seedtype": "Семенна тип", "seedtype_alert_content": "Споделянето на семена с други портфейли е възможно само с BIP39 Seedtype.", "seedtype_alert_title": "Сигнал за семена", + "select_a_wallet": "Изберете портфейл", "select_backup_file": "Избор на резервно копие", "select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.", "select_destination": "Моля, изберете дестинация за архивния файл.", @@ -906,6 +907,7 @@ "swap": "Разметка", "sweeping_wallet": "Метещ портфейл", "sweeping_wallet_alert": "Това не трябва да отнема много време. Не оставяйте този екран или пометените средства могат да бъдат загубени.", + "switch_wallet": "Превключвайте портфейла", "switchToETHWallet": "Моля, преминете към портфейл Ethereum и опитайте отново", "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)", "symbol": "Символ", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 8cae2a57e..360a51374 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -774,6 +774,7 @@ "seedtype": "SeedType", "seedtype_alert_content": "Sdílení semen s jinými peněženkami je možné pouze u BIP39 SeedType.", "seedtype_alert_title": "Upozornění seedtype", + "select_a_wallet": "Vyberte peněženku", "select_backup_file": "Vybrat soubor se zálohou", "select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.", "select_destination": "Vyberte cíl pro záložní soubor.", @@ -906,6 +907,7 @@ "swap": "Swap", "sweeping_wallet": "Zametací peněženka", "sweeping_wallet_alert": "To by nemělo trvat dlouho. Nenechávejte tuto obrazovku, jinak mohou být ztraceny prostředky.", + "switch_wallet": "Přepněte peněženku", "switchToETHWallet": "Přejděte na peněženku Ethereum a zkuste to znovu", "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)", "symbol": "Symbol", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index d3fffc7cd..de1cb16c0 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -775,6 +775,7 @@ "seedtype": "Seedtyp", "seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.", "seedtype_alert_title": "Seedype-Alarm", + "select_a_wallet": "Wählen Sie eine Brieftasche", "select_backup_file": "Sicherungsdatei auswählen", "select_buy_provider_notice": "Wählen Sie oben einen Anbieter kaufen. Sie können diese Seite überspringen, indem Sie Ihren Standard-Kaufanbieter in den App-Einstellungen festlegen.", "select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.", @@ -907,6 +908,7 @@ "swap": "Tauschen", "sweeping_wallet": "Wallet leeren", "sweeping_wallet_alert": "Das sollte nicht lange dauern. VERLASSEN SIE DIESEN BILDSCHIRM NICHT, ANDERNFALLS KÖNNEN DIE GELDER VERLOREN GEHEN", + "switch_wallet": "Brieftasche schalten", "switchToETHWallet": "Bitte wechseln Sie zu einem Ethereum-Wallet und versuchen Sie es erneut", "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)", "symbol": "Symbol", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 5a07657e3..96462b8f4 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -775,6 +775,7 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Sharing seeds with other wallets is only possible with BIP39 SeedType.", "seedtype_alert_title": "SeedType Alert", + "select_a_wallet": "Select a wallet", "select_backup_file": "Select backup file", "select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.", "select_destination": "Please select destination for the backup file.", @@ -907,6 +908,7 @@ "swap": "Swap", "sweeping_wallet": "Sweeping wallet", "sweeping_wallet_alert": "This shouldn’t take long. DO NOT LEAVE THIS SCREEN OR THE SWEPT FUNDS MAY BE LOST.", + "switch_wallet": "Switch Wallet", "switchToETHWallet": "Please switch to an Ethereum wallet and try again", "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)", "symbol": "Symbol", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index aac68d64e..c0381276a 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -775,6 +775,7 @@ "seedtype": "Tipos de semillas", "seedtype_alert_content": "Compartir semillas con otras billeteras solo es posible con semillas bip39 - un tipo específico de semilla.", "seedtype_alert_title": "Alerta de tipo de semillas", + "select_a_wallet": "Seleccione una billetera", "select_backup_file": "Seleccionar archivo de respaldo", "select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.", "select_destination": "Selecciona el destino del archivo de copia de seguridad.", @@ -907,6 +908,7 @@ "swap": "Cambio", "sweeping_wallet": "Barrer billetera (gastar todos los fondos disponibles)", "sweeping_wallet_alert": "Esto no debería llevar mucho tiempo. NO DEJES ESTA PANTALLA O SE PUEDEN PERDER LOS FONDOS BARRIDOS", + "switch_wallet": "Billetera", "switchToETHWallet": "Cambia a una billetera Ethereum e inténtelo nuevamente.", "switchToEVMCompatibleWallet": "Cambia a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)", "symbol": "Símbolo", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index a43e8eb3f..a5844658c 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -71,7 +71,7 @@ "auto_generate_subaddresses": "Générer automatiquement des sous-adresses", "automatic": "Automatique", "available_balance": "Solde disponible", - "available_balance_description": "Le "Solde disponible" ou "Solde confirmé" sont les montants que vous pouvez dépenser immédiatement. Si des fonds apparaissent dans le solde inférieur mais pas dans le solde supérieur, vous devez attendre quelques minutes pour que les fonds entrants obtiennent plus de confirmations réseau. Une fois qu’ils ont reçu plus de confirmations, ils seront utilisables.", + "available_balance_description": "Le Solde disponible ou Solde confirmé sont les montants que vous pouvez dépenser immédiatement. Si des fonds apparaissent dans le solde inférieur mais pas dans le solde supérieur, vous devez attendre quelques minutes pour que les fonds entrants obtiennent plus de confirmations réseau. Une fois qu’ils ont reçu plus de confirmations, ils seront utilisables.", "avg_savings": "Économies moy.", "awaitDAppProcessing": "Veuillez attendre que l'application décentralisée (dApp) termine le traitement.", "awaiting_payment_confirmation": "En attente de confirmation de paiement", @@ -774,6 +774,7 @@ "seedtype": "Type de graine", "seedtype_alert_content": "Le partage de graines avec d'autres portefeuilles n'est possible qu'avec le type de graine BIP39.", "seedtype_alert_title": "Alerte Type de Graine", + "select_a_wallet": "Sélectionnez un portefeuille", "select_backup_file": "Sélectionnez le fichier de sauvegarde", "select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.", "select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.", @@ -906,6 +907,7 @@ "swap": "Échanger", "sweeping_wallet": "Portefeuille (wallet) de consolidation", "sweeping_wallet_alert": "Cela ne devrait pas prendre longtemps. NE QUITTEZ PAS CET ÉCRAN OU LES FONDS TRANSFÉRÉS POURRAIENT ÊTRE PERDUS.", + "switch_wallet": "Portefeuille de commutation", "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer", "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)", "symbol": "Symbole", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index b797d475a..7074a05f3 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -776,6 +776,7 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Raba tsaba tare da sauran wallets yana yiwuwa ne kawai tare da Bip39 seedtype.", "seedtype_alert_title": "Seedtype farke", + "select_a_wallet": "Zaɓi Wallet", "select_backup_file": "Zaɓi fayil ɗin madadin", "select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.", "select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.", @@ -908,6 +909,7 @@ "swap": "Musya", "sweeping_wallet": "Kashi na kasa", "sweeping_wallet_alert": "Wannan ba zai samu lokacin mai tsaski. KADA KA SAMU KUNGIYARAN KUHON, ZAMAN DADIN BANKUNCI ZAI HAŘA", + "switch_wallet": "Zanen rubutu", "switchToETHWallet": "Da fatan za a canza zuwa walat ɗin Ethereum kuma a sake gwadawa", "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)", "symbol": "Alama", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 34b1b5a4f..a767c3040 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -776,6 +776,7 @@ "seedtype": "बीज", "seedtype_alert_content": "अन्य पर्स के साथ बीज साझा करना केवल BIP39 सीडटाइप के साथ संभव है।", "seedtype_alert_title": "बीजगणित अलर्ट", + "select_a_wallet": "एक वॉलेट का चयन करें", "select_backup_file": "बैकअप फ़ाइल का चयन करें", "select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।", "select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।", @@ -908,6 +909,7 @@ "swap": "बदलना", "sweeping_wallet": "स्वीपिंग वॉलेट", "sweeping_wallet_alert": "इसमें अधिक समय नहीं लगना चाहिए। इस स्क्रीन को न छोड़ें या स्वैप्ट फंड खो सकते हैं", + "switch_wallet": "स्विच वॉलेट", "switchToETHWallet": "कृपया एथेरियम वॉलेट पर स्विच करें और पुनः प्रयास करें", "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)", "symbol": "प्रतीक", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 248c3a05b..830d54927 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -774,6 +774,7 @@ "seedtype": "Sjemenska vrsta", "seedtype_alert_content": "Dijeljenje sjemena s drugim novčanicima moguće je samo s BIP39 sjemenom.", "seedtype_alert_title": "Upozorenje o sjemenu", + "select_a_wallet": "Odaberite novčanik", "select_backup_file": "Odaberite datoteku sigurnosne kopije", "select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.", "select_destination": "Odaberite odredište za datoteku sigurnosne kopije.", @@ -906,6 +907,7 @@ "swap": "Mijenjati", "sweeping_wallet": "Čisti novčanik", "sweeping_wallet_alert": "Ovo ne bi trebalo dugo trajati. NE NAPUŠTAJTE OVAJ ZASLON INAČE SE POBREŠENA SREDSTVA MOGU IZGUBITI", + "switch_wallet": "PRIKLJUČAK WALINE", "switchToETHWallet": "Prijeđite na Ethereum novčanik i pokušajte ponovno", "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)", "symbol": "Simbol", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 9b65860c7..837a72736 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -773,6 +773,7 @@ "seedtype": "Սերմի տեսակ", "seedtype_alert_content": "Այլ դրամապանակներով սերմերի փոխանակումը հնարավոր է միայն BIP39 SEEDTYPE- ով:", "seedtype_alert_title": "SEEDTYPE ALERT", + "select_a_wallet": "Ընտրեք դրամապանակ", "select_backup_file": "Ընտրել կրկնօրինակ ֆայլ", "select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով", "select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը", @@ -904,6 +905,7 @@ "swap": "Փոխանակել", "sweeping_wallet": "Դրամապանակը մաքրվում է", "sweeping_wallet_alert": "Սա չի տևի երկար։ Խնդրում ենք չլքել այս էկրանը կամ մաքրված միջոցները կկորչեն։", + "switch_wallet": "Անջատեք դրամապանակը", "switchToETHWallet": "Խնդրում ենք անցնել Ethereum դրամապանակ և փորձել կրկին", "switchToEVMCompatibleWallet": "Խնդրում ենք անցնել EVM համատեղելի դրամապանակ և փորձել կրկին (Ethereum, Polygon)", "symbol": "Նշան", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index cd203b5f9..675edf937 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -777,6 +777,7 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Berbagi biji dengan dompet lain hanya dimungkinkan dengan BIP39 seedtype.", "seedtype_alert_title": "Peringatan seedtype", + "select_a_wallet": "Pilih dompet", "select_backup_file": "Pilih file cadangan", "select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.", "select_destination": "Silakan pilih tujuan untuk file cadangan.", @@ -909,6 +910,7 @@ "swap": "Menukar", "sweeping_wallet": "Dompet menyapu", "sweeping_wallet_alert": "Ini seharusnya tidak memakan waktu lama. Jangan tinggalkan layar ini atau dana swept mungkin hilang.", + "switch_wallet": "Switch Wallet", "switchToETHWallet": "Silakan beralih ke dompet Ethereum dan coba lagi", "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)", "symbol": "Simbol", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 5b16f02de..35dfc565a 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -775,6 +775,7 @@ "seedtype": "Seedtype", "seedtype_alert_content": "La condivisione di semi con altri portafogli è possibile solo con Bip39 SeedType.", "seedtype_alert_title": "Avviso seedType", + "select_a_wallet": "Seleziona un portafoglio", "select_backup_file": "Seleziona file di backup", "select_buy_provider_notice": "Seleziona un provider di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.", "select_destination": "Seleziona la destinazione per il file di backup.", @@ -907,6 +908,7 @@ "swap": "Scambio", "sweeping_wallet": "Svuota portafoglio", "sweeping_wallet_alert": "Questo non dovrebbe richiedere molto tempo. NON LASCIARE QUESTA SCHERMATA O I FONDI DA TRASFERIRE POTREBBERO ANDARE PERSI", + "switch_wallet": "Switch Porthet", "switchToETHWallet": "Passa a un portafoglio Ethereum e riprova", "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)", "symbol": "Simbolo", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index bfef440a4..0e163cf2c 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -775,6 +775,7 @@ "seedtype": "SeedType", "seedtype_alert_content": "他の財布と種子を共有することは、BIP39 SeedTypeでのみ可能です。", "seedtype_alert_title": "SeedTypeアラート", + "select_a_wallet": "ウォレットを選択します", "select_backup_file": "バックアップファイルを選択", "select_buy_provider_notice": "上記の購入プロバイダーを選択してください。デフォルトの購入プロバイダーをアプリ設定で設定して、この画面をスキップできます。", "select_destination": "バックアップファイルの保存先を選択してください。", @@ -907,6 +908,7 @@ "swap": "スワップ", "sweeping_wallet": "スイープウォレット", "sweeping_wallet_alert": "これには時間がかかりません。この画面から離れないでください。そうしないと、スイープ ファンドが失われる可能性があります", + "switch_wallet": "スイッチウォレット", "switchToETHWallet": "イーサリアムウォレットに切り替えてもう一度お試しください", "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)", "symbol": "シンボル", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index a6f55d69a..62b860591 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -775,6 +775,7 @@ "seedtype": "시드 유형", "seedtype_alert_content": "다른 지갑과의 시드 공유는 BIP39 시드 유형에서만 가능합니다.", "seedtype_alert_title": "시드 유형 알림", + "select_a_wallet": "지갑을 선택하십시오", "select_backup_file": "백업 파일 선택", "select_buy_provider_notice": "위에서 구매 제공 업체를 선택하세요. 앱 설정에서 기본 구매 제공 업체를 설정하여 이 화면을 건너뛸 수 있습니다.", "select_destination": "백업 파일의 대상을 선택하세요.", @@ -907,6 +908,7 @@ "swap": "스왑", "sweeping_wallet": "지갑 스윕 중", "sweeping_wallet_alert": "오래 걸리지 않습니다. 이 화면을 떠나거나 스윕된 자금을 잃을 수 있습니다.", + "switch_wallet": "스위치 지갑", "switchToETHWallet": "이더리움 지갑으로 전환하고 다시 시도하세요", "switchToEVMCompatibleWallet": "EVM 호환 지갑(이더리움, 폴리곤)으로 전환하고 다시 시도하세요", "symbol": "심볼", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 2e5c2fb61..7fe3c227b 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -774,6 +774,7 @@ "seedtype": "မျိုးပွားခြင်း", "seedtype_alert_content": "အခြားပိုက်ဆံအိတ်များနှင့်မျိုးစေ့များကိုမျှဝေခြင်းသည် BIP39 sebyspe ဖြင့်သာဖြစ်သည်။", "seedtype_alert_title": "ပျိုးပင်သတိပေးချက်", + "select_a_wallet": "ပိုက်ဆံအိတ်တစ်ခုရွေးပါ", "select_backup_file": "အရန်ဖိုင်ကို ရွေးပါ။", "select_buy_provider_notice": "အပေါ်ကဝယ်သူတစ် ဦး ကိုရွေးချယ်ပါ။ သင်၏ default 0 ယ်သူအား app settings တွင် setting လုပ်ခြင်းဖြင့်ဤ screen ကိုကျော်သွားနိုင်သည်။", "select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။", @@ -906,6 +907,7 @@ "swap": "လဲလှယ်", "sweeping_wallet": "ိုက်ဆံအိတ် တံမြက်လှည်း", "sweeping_wallet_alert": "ဒါက ကြာကြာမခံသင့်ပါဘူး။ ဤစခရင်ကို ချန်မထားပါနှင့် သို့မဟုတ် ထုတ်ယူထားသော ရန်ပုံငွေများ ဆုံးရှုံးနိုင်သည်", + "switch_wallet": "Wallet switch", "switchToETHWallet": "ကျေးဇူးပြု၍ Ethereum ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ။", "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)", "symbol": "သင်္ကေတ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index bb4c5f84e..7394bdec1 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -774,6 +774,7 @@ "seedtype": "Zaadtype", "seedtype_alert_content": "Het delen van zaden met andere portefeuilles is alleen mogelijk met BIP39 SeedType.", "seedtype_alert_title": "Zaadtype alert", + "select_a_wallet": "Selecteer een portemonnee", "select_backup_file": "Selecteer een back-upbestand", "select_buy_provider_notice": "Selecteer hierboven een koopprovider. U kunt dit scherm overslaan door uw standaard kopenprovider in te stellen in app -instellingen.", "select_destination": "Selecteer de bestemming voor het back-upbestand.", @@ -906,6 +907,7 @@ "swap": "Ruil", "sweeping_wallet": "Vegende portemonnee", "sweeping_wallet_alert": "Dit duurt niet lang. VERLAAT DIT SCHERM NIET, ANDERS KAN HET SWEPT-GELD VERLOREN WORDEN", + "switch_wallet": "Schakel portemonnee", "switchToETHWallet": "Schakel over naar een Ethereum-portemonnee en probeer het opnieuw", "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)", "symbol": "Symbool", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index e70228f8d..595f5855f 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -774,6 +774,7 @@ "seedtype": "Sedtype", "seedtype_alert_content": "Dzielenie się nasionami z innymi portfelami jest możliwe tylko z BIP39 sededType.", "seedtype_alert_title": "Ustanowienie typu sedype", + "select_a_wallet": "Wybierz portfel", "select_backup_file": "Wybierz plik kopii zapasowej", "select_buy_provider_notice": "Wybierz powyższe dostawcę zakupu. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę zakupu w ustawieniach aplikacji.", "select_destination": "Wybierz miejsce docelowe dla pliku kopii zapasowej.", @@ -906,6 +907,7 @@ "swap": "Wymiana", "sweeping_wallet": "Zamiatanie portfela", "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", + "switch_wallet": "Portfel przełącz", "switchToETHWallet": "Przejdź na portfel Ethereum i spróbuj ponownie", "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)", "symbol": "Symbol", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 58c5a88f9..a3ba6669f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -776,6 +776,7 @@ "seedtype": "SeedType", "seedtype_alert_content": "Compartilhar sementes com outras carteiras só é possível com o BIP39 SeedType.", "seedtype_alert_title": "Alerta de SeedType", + "select_a_wallet": "Selecione uma carteira", "select_backup_file": "Selecione o arquivo de backup", "select_buy_provider_notice": "Selecione um provedor de compra acima. Você pode pular esta tela definindo seu provedor de compra padrão nas configurações de aplicativos.", "select_destination": "Selecione o destino para o arquivo de backup.", @@ -908,6 +909,7 @@ "swap": "Trocar", "sweeping_wallet": "Carteira varrendo", "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", + "switch_wallet": "CALLETA DE CURSÃO", "switchToETHWallet": "Mude para uma carteira Ethereum e tente novamente", "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)", "symbol": "Símbolo", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index ba2e0b88d..dcf3d526a 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -775,6 +775,7 @@ "seedtype": "SEEDTYPE", "seedtype_alert_content": "Обмен семенами с другими кошельками возможно только с BIP39 SeedType.", "seedtype_alert_title": "SEEDTYPE ALERT", + "select_a_wallet": "Выберите кошелек", "select_backup_file": "Выберите файл резервной копии", "select_buy_provider_notice": "Выберите поставщика покупки выше. Вы можете пропустить этот экран, установив поставщика покупки по умолчанию в настройках приложения.", "select_destination": "Пожалуйста, выберите место для файла резервной копии.", @@ -907,6 +908,7 @@ "swap": "Менять", "sweeping_wallet": "Подметание кошелька", "sweeping_wallet_alert": "Это не должно занять много времени. НЕ ПОКИДАЙТЕ ЭТОТ ЭКРАН, ИНАЧЕ ВЫЧИСЛЕННЫЕ СРЕДСТВА МОГУТ БЫТЬ ПОТЕРЯНЫ", + "switch_wallet": "Переключить кошелек", "switchToETHWallet": "Пожалуйста, переключитесь на кошелек Ethereum и повторите попытку.", "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon).", "symbol": "Символ", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 9378b417a..62c290ce8 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -774,6 +774,7 @@ "seedtype": "เมล็ดพันธุ์", "seedtype_alert_content": "การแบ่งปันเมล็ดกับกระเป๋าเงินอื่น ๆ เป็นไปได้เฉพาะกับ bip39 seedtype", "seedtype_alert_title": "การแจ้งเตือน seedtype", + "select_a_wallet": "เลือกกระเป๋าเงิน", "select_backup_file": "เลือกไฟล์สำรอง", "select_buy_provider_notice": "เลือกผู้ให้บริการซื้อด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการซื้อเริ่มต้นในการตั้งค่าแอป", "select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง", @@ -906,6 +907,7 @@ "swap": "แลกเปลี่ยน", "sweeping_wallet": "กวาดกระเป๋าสตางค์", "sweeping_wallet_alert": "การดำเนินการนี้ใช้เวลาไม่นาน อย่าออกจากหน้าจอนี้ มิฉะนั้นเงินที่กวาดไปอาจสูญหาย", + "switch_wallet": "สลับกระเป๋าเงิน", "switchToETHWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงิน Ethereum แล้วลองอีกครั้ง", "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)", "symbol": "เครื่องหมาย", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 74d102145..e8f9f3d84 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -774,6 +774,7 @@ "seedtype": "Seed type", "seedtype_alert_content": "Ang pagbabahagi ng mga buto sa iba pang mga pitaka ay posible lamang sa bip39 seedtype.", "seedtype_alert_title": "Alerto ng Seedtype", + "select_a_wallet": "Pumili ng isang pitaka", "select_backup_file": "Piliin ang backup na file", "select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup na file.", @@ -906,6 +907,7 @@ "swap": "Palitan", "sweeping_wallet": "Sweeping wallet", "sweeping_wallet_alert": "Hindi ito dapat magtagal. HUWAG iwanan ang screen na ito o maaaring mawala ang mga pondo.", + "switch_wallet": "Lumipat ng pitaka", "switchToETHWallet": "Mangyaring lumipat sa isang Ethereum wallet at subukang muli", "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)", "symbol": "Simbolo", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 29e25e376..0bdeaf4a3 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -774,6 +774,7 @@ "seedtype": "Tohum", "seedtype_alert_content": "Tohumları diğer cüzdanlarla paylaşmak sadece BIP39 tohumu ile mümkündür.", "seedtype_alert_title": "SeedType uyarısı", + "select_a_wallet": "Bir Cüzdan Seçin", "select_backup_file": "Yedek dosyası seç", "select_buy_provider_notice": "Yukarıda bir satın alma sağlayıcısı seçin. App ayarlarında varsayılan satın alma sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", "select_destination": "Lütfen yedekleme dosyası için hedef seçin.", @@ -906,6 +907,7 @@ "swap": "Takas", "sweeping_wallet": "Süpürme cüzdanı", "sweeping_wallet_alert": "Bu uzun sürmemeli. BU EKRANDAN BIRAKMAYIN YOKSA SÜPÜRÜLEN FONLAR KAYBOLABİLİR", + "switch_wallet": "Cüzdan", "switchToETHWallet": "Lütfen bir Ethereum cüzdanına geçin ve tekrar deneyin", "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)", "symbol": "Sembol", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 2b5d9a2a1..f6d4e0609 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -775,6 +775,7 @@ "seedtype": "Насіннєвий тип", "seedtype_alert_content": "Спільний доступ до інших гаманців можливе лише за допомогою BIP39 Seedtype.", "seedtype_alert_title": "Попередження насінника", + "select_a_wallet": "Виберіть гаманець", "select_backup_file": "Виберіть файл резервної копії", "select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.", "select_destination": "Виберіть місце призначення для файлу резервної копії.", @@ -907,6 +908,7 @@ "swap": "Обміняти", "sweeping_wallet": "Підмітаня гаманця", "sweeping_wallet_alert": "Це не повинно зайняти багато часу. НЕ ЗАЛИШАЙТЕ ЦЬОГО ЕКРАНУ, АБО КОШТИ МОЖУТЬ БУТИ ВТРАЧЕНІ", + "switch_wallet": "Перемикач гаманця", "switchToETHWallet": "Перейдіть на гаманець Ethereum і повторіть спробу", "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)", "symbol": "символ", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 628eba18a..a3c3ab6ac 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -776,6 +776,7 @@ "seedtype": "سیڈ ٹائپ", "seedtype_alert_content": "دوسرے بٹوے کے ساتھ بیجوں کا اشتراک صرف BIP39 بیج ٹائپ کے ساتھ ہی ممکن ہے۔", "seedtype_alert_title": "سیڈ ٹائپ الرٹ", + "select_a_wallet": "ایک پرس منتخب کریں", "select_backup_file": "بیک اپ فائل کو منتخب کریں۔", "select_buy_provider_notice": "اوپر خریدنے والا خریدنے والا منتخب کریں۔ آپ ایپ کی ترتیبات میں اپنے پہلے سے طے شدہ خریدنے والے کو ترتیب دے کر اس اسکرین کو چھوڑ سکتے ہیں۔", "select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ", @@ -908,6 +909,7 @@ "swap": "تبادلہ", "sweeping_wallet": "جھاڑو دینے والا پرس", "sweeping_wallet_alert": "اس میں زیادہ وقت نہیں لینا چاہئے۔ اس اسکرین کو مت چھوڑیں یا بہہ جانے والے فنڈز ضائع ہوسکتے ہیں۔", + "switch_wallet": "سوئچ پرس", "switchToETHWallet": "۔ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ Ethereum ﻡﺮﮐ ﮦﺍﺮﺑ", "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ ", "symbol": "ﺖﻣﻼﻋ", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index f1ba731f3..bb1f7737c 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -772,6 +772,7 @@ "seedtype": "Loại hạt giống", "seedtype_alert_content": "Chia sẻ hạt giống với ví khác chỉ có thể với BIP39 SeedType.", "seedtype_alert_title": "Cảnh báo hạt giống", + "select_a_wallet": "Chọn một ví", "select_backup_file": "Chọn tệp sao lưu", "select_buy_provider_notice": "Chọn nhà cung cấp mua ở trên. Bạn có thể bỏ qua màn hình này bằng cách thiết lập nhà cung cấp mua mặc định trong cài đặt ứng dụng.", "select_destination": "Vui lòng chọn đích cho tệp sao lưu.", @@ -903,6 +904,7 @@ "swap": "Tráo đổi", "sweeping_wallet": "Quét ví", "sweeping_wallet_alert": "Việc này không nên mất nhiều thời gian. KHÔNG RỜI KHỎI MÀN HÌNH NÀY HOẶC CÁC KHOẢN TIỀN ĐƯỢC QUÉT CÓ THỂ BỊ MẤT.", + "switch_wallet": "Chuyển ví", "switchToETHWallet": "Vui lòng chuyển sang ví Ethereum và thử lại", "switchToEVMCompatibleWallet": "Vui lòng chuyển sang ví tương thích EVM và thử lại (Ethereum, Polygon)", "symbol": "Ký hiệu", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 9af9de32d..4e3bfc466 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -775,6 +775,7 @@ "seedtype": "Irugbin-seetypu", "seedtype_alert_content": "Pinpin awọn irugbin pẹlu awọn gedo miiran ṣee ṣe pẹlu Bip39 irugbin.", "seedtype_alert_title": "Ṣajọpọ Seeytype", + "select_a_wallet": "Yan apamọwọ kan", "select_backup_file": "Select backup file", "select_buy_provider_notice": "Yan olupese Ra loke. O le skii iboju yii nipa ṣiṣeto olupese rẹ ni awọn eto App.", "select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.", @@ -907,6 +908,7 @@ "swap": "Eepo", "sweeping_wallet": "Fi owo iwe iwe wofo", "sweeping_wallet_alert": "Yio kọja pada si ikan yii. Kì yoo daadaa leede yii tabi owo ti o ti fi se iwe iwe naa yoo gbe.", + "switch_wallet": "Yipada apamọwọ", "switchToETHWallet": "Jọwọ yipada si apamọwọ Ethereum ki o tun gbiyanju lẹẹkansi", "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)", "symbol": "Aami", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 5a629c264..fb7a366db 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -774,6 +774,7 @@ "seedtype": "籽粒", "seedtype_alert_content": "只有BIP39籽粒可以与其他钱包共享种子。", "seedtype_alert_title": "籽粒警报", + "select_a_wallet": "选择一个钱包", "select_backup_file": "选择备份文件", "select_buy_provider_notice": "在上面选择买入提供商。您可以通过在应用程序设置中设置默认的购买提供商来跳过此屏幕。", "select_destination": "请选择备份文件的目的地。", @@ -906,6 +907,7 @@ "swap": "交换", "sweeping_wallet": "扫一扫钱包", "sweeping_wallet_alert": "\n这应该不会花很长时间。请勿离开此屏幕,否则可能会丢失所掠取的资金", + "switch_wallet": "开关钱包", "switchToETHWallet": "请切换到以太坊钱包并重试", "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)", "symbol": "象征", diff --git a/test/core/universal_address_detector_test.dart b/test/core/universal_address_detector_test.dart new file mode 100644 index 000000000..992fecbb1 --- /dev/null +++ b/test/core/universal_address_detector_test.dart @@ -0,0 +1,242 @@ +import 'package:cake_wallet/core/universal_address_detector.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('UniversalAddressDetector', () { + group('Bitcoin Address Detection', () { + test('detects Bitcoin P2PKH address', () { + const address = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.btc); + expect(result.detectedWalletType, WalletType.bitcoin); + expect(result.address, address); + }); + + test('detects Bitcoin P2SH address', () { + const address = '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.btc); + expect(result.detectedWalletType, WalletType.bitcoin); + expect(result.address, address); + }); + + test('detects Bitcoin Bech32 address', () { + const address = 'bc1q56x5hha4mm35wmnqmj8ajkgxykf9cnjmrv3tmj'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.btc); + expect(result.detectedWalletType, WalletType.bitcoin); + expect(result.address, address); + }); + + test('detects Bitcoin URI with amount', () { + const uri = 'bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=0.001'; + final result = UniversalAddressDetector.detectAddress(uri); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.btc); + expect(result.detectedWalletType, WalletType.bitcoin); + expect(result.address, '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'); + expect(result.amount, '0.001'); + }); + }); + + group('Monero Address Detection', () { + test('detects Monero address', () { + const address = + '48HHtdQvxyH5jwX5N6fAQD6rxx7EQT7GKZvsixtgJRkRaD8wHqwKp6eGSuStiUN5MHXR19vF3W4Jc7MumryYTTH7LGTfanS'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.xmr); + expect(result.detectedWalletType, WalletType.monero); + expect(result.address, address); + }); + + test('detects Monero integrated address', () { + const address = + '88HHtdQvxyH5jwX5N6fAQD6rxx7EQT7GKZvsixtgJRkRaD8wHqwKp6eGSuStiUN5MHXR19vF3W4Jc7MumryYTTH7LGTfanS'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.xmr); + expect(result.detectedWalletType, WalletType.monero); + expect(result.address, address); + }); + }); + + group('Ethereum Address Detection', () { + test('detects Ethereum address', () { + const address = '0xAE3A8C650CDFad88e87621F8371642bd4B578601'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.eth); + expect(result.detectedWalletType, WalletType.ethereum); + expect(result.address, address); + }); + + test('detects Ethereum URI', () { + const uri = 'ethereum:0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6?value=1000000000000000000'; + final result = UniversalAddressDetector.detectAddress(uri); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.eth); + expect(result.detectedWalletType, WalletType.ethereum); + expect(result.address, '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6'); + expect(result.amount, '1'); + }); + }); + + group('Litecoin Address Detection', () { + test('detects Litecoin Bech32 address', () { + const address = 'ltc1qk4ewr0fjgltsvwymfz7az66q2w73qj0z06cj36'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.ltc); + expect(result.detectedWalletType, WalletType.litecoin); + expect(result.address, address); + }); + }); + + group('Bitcoin Cash Address Detection', () { + test('detects Bitcoin Cash address', () { + const address = 'bitcoincash:qqdjjymdpfmqzq69t4vdcxsmrzmlzlwgaucwm878p5'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.bch); + expect(result.detectedWalletType, WalletType.bitcoinCash); + expect(result.address, 'qqdjjymdpfmqzq69t4vdcxsmrzmlzlwgaucwm878p5'); + }); + + test('detects Bitcoin Cash address without prefix', () { + const address = 'qqdjjymdpfmqzq69t4vdcxsmrzmlzlwgaucwm878p5'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.bch); + expect(result.detectedWalletType, WalletType.bitcoinCash); + expect(result.address, address); + }); + }); + + group('Nano Address Detection', () { + test('detects Nano address', () { + const address = 'nano_3gsnr4a3ku3k9hjczobbr5fhhz94c66yczxbpjukeyy3edpxiyp1tqi8angm'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.nano); + expect(result.detectedWalletType, WalletType.nano); + expect(result.address, address); + }); + }); + + group('Solana Address Detection', () { + test('detects Solana address', () { + const address = '7TSTXnQZxQDA4JoNqs4DqVqE7oNWk2kmHkMT6DD6vw2S'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.sol); + expect(result.detectedWalletType, WalletType.solana); + expect(result.address, address); + }); + }); + + group('Tron Address Detection', () { + test('detects Tron address', () { + const address = 'TF7yQKp7pwLBSXBXthcrXMqTyjtvkJK28V'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.trx); + expect(result.detectedWalletType, WalletType.tron); + expect(result.address, address); + }); + }); + + group('Wownero Address Detection', () { + test('detects Wownero address', () { + const address = + 'WW3uV9iygJjefkU5bmzeoCFkqZEwg1xC92BKcUFJMcSVF1HQoK4ehDTJScTU28CYvJaZ9sZt5xH8qF1n6vXqXE9G2KMC9JsKh'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.wow); + expect(result.detectedWalletType, WalletType.wownero); + expect(result.address, address); + }); + }); + + group('Zano Address Detection', () { + test('detects Zano address', () { + const address = + 'ZxDREZKHjUAUkRpWrHV9AjJHe12RhtoY9TjUUx2EznNNdwJK9yiHEhuQkqGYnyDXRnFC3Uehu6kLGZmiQab8fWoQ1JrLsuJ1i'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.zano); + expect(result.detectedWalletType, WalletType.zano); + expect(result.address, address); + }); + + test('detects Zano alias', () { + const address = '@testuser'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.zano); + expect(result.detectedWalletType, WalletType.zano); + expect(result.address, address); + }); + }); + + group('Decred Address Detection', () { + test('detects Decred address', () { + const address = 'DsW4ZtRV1DcPCuKXVVtGnCy3AtAZHg5N8nR'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, true); + expect(result.detectedCurrency, CryptoCurrency.dcr); + expect(result.detectedWalletType, WalletType.decred); + expect(result.address, address); + }); + }); + + group('Invalid Address Detection', () { + test('handles empty input', () { + const address = ''; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, false); + expect(result.errorMessage, 'Empty input provided'); + }); + + test('handles whitespace only', () { + const address = ' '; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, false); + expect(result.errorMessage, 'Empty input provided'); + }); + + test('handles random text', () { + const address = 'this_is_not_an_address_123'; + final result = UniversalAddressDetector.detectAddress(address); + + expect(result.isValid, false); + expect(result.errorMessage, 'Unable to detect valid cryptocurrency address'); + }); + }); + }); +}