From e4672c75b79ecca6ddbbc4983d0dcc1de5a4163c Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 1 Oct 2025 17:20:40 +0200 Subject: [PATCH] CW-1135-add-support-for-bitbox-hww (#2476) * chore: add ffi and ffigen dependencies to pubspec.yaml * refactor: replace method channel with FFI implementation for MWeb module * fix: correct `cd` command in build_mwebd.sh script * chore: add go.sum file with dependency checksums * fix: ensure ANDROID_NDK_VERSION is set before building and dynamically update NDK_BIN path * fix: update NDK_BIN path to dynamically detect platform in build_mwebd.sh script * refactor: remove unused mwebd dependencies and clean up CwMwebPlugin code * Update scripts/android/build_mwebd.sh Co-authored-by: cyan * refactor: replace iOS MoneroWallet framework with Mwebd.xcframework and clean up plugin implementation [skip-ci] * refactor: extract hardware wallet service abstraction * refactor: extend BitcoinHardwareWalletService from HardwareWalletService and override account retrieval method * feat: add BitBox hardware wallet support and implement manufacturer selection page with custom UI components * refactor: update manufacturer selection UI and integrate custom gradient styles for ManufacturerOptionTile widget * fix: mwebd ffi ios * feat: add BitBox hardware wallet support, update derivation paths, and refactor hardware wallet credential implementations * feat: add bitbox support and refactor the old ledger implementation * feat: add BitBox hardware wallet support and refactor hardware wallet service implementations * feat: refactor hardware wallet support to introduce abstraction for BitBox and Ledger; update device scanning, initialization, and UI integration * fix: BitBox hardware wallet ethereum transaction signing * chore: update BitBox Flutter path references and include build steps in CI workflows * chore: update CI workflows to include `--dont-install` flag for BitBox Flutter builds * refactor: replace if-else logic with switch-case for HardwareWalletViewModel factory * feat: refactor hardware wallet services to introduce shared interface, improve ledger and bitbox implementations, and support additional transaction signing operations * feat: add SVG assets for hardware wallet manufacturers (Foundation, Keystone, SeedSigner) * feat: redesign hardware wallet restoration flow with improved routing, UI updates, and manufacturer-specific device selection handling * fix: validate required BIP353 fields in transaction builder to prevent potential null value errors * feat: improve BitBox transaction signing logs with detailed PSBT information * refactor: update MWebFfi class to use non-nullable `lib` and improve address debug logging * feat: add BitBox coin type constant and improve hardware wallet service extensibility * chore: remove unused iOS/Android build scripts for mwebd * feat: add BitBox SVG asset and update localization for hardware wallet connection instructions * feat: update manufacturer tags and add Toast Easter egg for device selection interactions * refactor: replace LedgerViewModel with HardwareWalletViewModel across providers and improve hardware wallet handling * refactor: replace mweb.so with libmweb.so, use sync methods in MWebFfi, and update iOS/Android frameworks/scripts accordingly * feat: add Ledger Nano X SVG asset to hardware wallet resources * chore: unify mwebd build process in workflows, remove unused gomobile init command * refactor: remove redundant print statement and unnecessary whitespace * chore: add `gomobile init` to Android PR test build workflow * chore: update Android PR test build workflow and add file existence check in `build_bitbox_flutter.sh` * chore: override `ledger_usb_plus` dependency in pubspec_base.yaml * feat: add verbose logging for BitBox connection and initialization process * Update lib/view_model/hardware_wallet/bitbox_view_model.dart [skip ci] * CW-1206-refactor-hardware-wallets-code (#2507) * feat: add support for new hardware wallet types (Cupcake, Coldcard, SeedSigner) and improve QR Code handling - Introduced new hardware wallet types (Cupcake, Coldcard, SeedSigner) with corresponding assets and localization updates. - Updated QR Code format selection for hardware wallets with new formats (BCUR, BBQR) specific to devices. - Improved hardware wallet restoration flow to include airgapped wallet handling via QR Code scanning. - Refactored routing, view models, and dependency injection to accommodate new hardware wallet types. - Enhanced UI for device manufacturer selection and QR Code format dialogs. * feat: update hardware wallet assets and restoration flow * feat: add changing airgapped wallet type * feat: add Cupcake Man SVG asset to hardware wallet resources * refactor: update Litecoin receive options to require wallet parameter and handle hardware wallets * fix: debugging keystone integration feat: support for zpub view only restore * fix: seed signer restore * fix: disable bitbox restoring (for now) [skip-ci] * refactor: rename `isCupcake` to `isBitcoinViewOnly` for clarity [skip ci] --------- Co-authored-by: Czarek Nakamoto Co-authored-by: Omar Hatem * refactor: standardize hardware wallet connection handling across wallet types --------- Co-authored-by: cyan Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build_android.yml | 8 + .github/workflows/pr_test_build_linux.yml | 7 + assets/images/hardware_wallet/bitbox_man.svg | 8 + .../images/hardware_wallet/coldcard_man.svg | 19 ++ assets/images/hardware_wallet/cupcake_man.svg | 4 + .../images/hardware_wallet/device_bitbox.svg | 3 + .../hardware_wallet/device_ledger_nano_x.svg | 3 + assets/images/hardware_wallet/device_qr.svg | 3 + .../images/hardware_wallet/foundation_man.svg | 48 +++++ .../images/hardware_wallet/keystone_man.svg | 13 ++ assets/images/hardware_wallet/ledger_man.svg | 3 + .../images/hardware_wallet/seedsigner_man.svg | 20 ++ assets/images/hardware_wallet/trezor_man.svg | 11 ++ .../lib/bitcoin_hardware_wallet_service.dart | 46 ----- cw_bitcoin/lib/bitcoin_wallet.dart | 25 +-- .../bitcoin_wallet_creation_credentials.dart | 1 + cw_bitcoin/lib/bitcoin_wallet_service.dart | 12 +- cw_bitcoin/lib/electrum_wallet.dart | 33 +++- cw_bitcoin/lib/hardware/bitbox_service.dart | 93 +++++++++ .../bitcoin_hardware_wallet_service.dart | 5 + .../lib/hardware/bitcoin_ledger_service.dart | 57 ++++++ .../litecoin_ledger_service.dart} | 15 +- cw_bitcoin/lib/psbt/transaction_builder.dart | 4 +- cw_bitcoin/lib/utils.dart | 3 +- cw_bitcoin/pubspec.lock | 11 +- cw_bitcoin/pubspec.yaml | 4 +- .../lib/hardware/device_connection_type.dart | 39 ++-- .../lib/hardware/hardware_wallet_service.dart | 13 ++ cw_core/lib/utils/zpub.dart | 46 +++++ cw_core/lib/wallet_base.dart | 2 + cw_core/lib/wallet_info.dart | 13 +- cw_core/pubspec.lock | 2 +- cw_core/pubspec.yaml | 4 + cw_evm/lib/evm_chain_client.dart | 1 + cw_evm/lib/evm_chain_wallet.dart | 11 +- .../lib/hardware/evm_bitbox_credentials.dart | 79 ++++++++ .../hardware/evm_chain_bitbox_service.dart | 32 +++ .../evm_chain_ledger_service.dart} | 6 +- .../evm_ledger_credentials.dart | 2 + cw_evm/pubspec.yaml | 2 + cw_monero/pubspec.lock | 10 +- ios/Podfile.lock | 7 + lib/bitcoin/cw_bitcoin.dart | 43 ++-- lib/buy/buy_provider.dart | 6 +- lib/buy/dfx/dfx_buy_provider.dart | 53 +++-- lib/buy/kryptonim/kryptonim.dart | 2 +- lib/buy/meld/meld_buy_provider.dart | 2 +- lib/buy/moonpay/moonpay_provider.dart | 2 +- lib/buy/onramper/onramper_buy_provider.dart | 3 +- lib/buy/robinhood/robinhood_buy_provider.dart | 63 +++--- lib/buy/wyre/wyre_buy_provider.dart | 2 +- lib/core/new_wallet_type_arguments.dart | 5 +- lib/di.dart | 73 +++++-- .../hardware_wallet_device.dart | 59 ++++-- lib/ethereum/cw_ethereum.dart | 28 +-- lib/polygon/cw_polygon.dart | 32 ++- .../on_authentication_state_change.dart | 6 +- lib/router.dart | 63 ++++-- lib/routes.dart | 2 + .../connect_device/connect_device_page.dart | 145 +++++++------- .../select_device_manufacturer_page.dart | 183 +++++++++++++++++ .../connect_device/widgets/device_tile.dart | 15 +- .../widgets/dotted_divider.dart | 36 ++++ .../widgets/manufacturer_option_tile.dart | 80 ++++++++ .../pages/balance/crypto_balance_widget.dart | 24 ++- lib/src/screens/dev/qr_tools_page.dart | 39 ++++ .../new_wallet/new_wallet_type_page.dart | 21 +- .../screens/receive/widgets/qr_widget.dart | 2 +- .../screens/restore/restore_options_page.dart | 98 ++++------ lib/src/screens/send/send_page.dart | 11 +- .../screens/settings/other_settings_page.dart | 82 +++++--- lib/src/screens/ur/animated_ur_page.dart | 3 +- lib/src/screens/ur/widgets/urqr.dart | 184 ++++++++---------- .../screens/wallet_list/wallet_list_page.dart | 5 +- .../dashboard/receive_option_view_model.dart | 2 +- lib/view_model/dev/qr_tools_view_model.dart | 81 ++++++++ .../hardware_wallet/bitbox_view_model.dart | 110 +++++++++++ .../hardware_wallet_view_model.dart | 27 +++ .../hardware_wallet/ledger_view_model.dart | 73 +++++-- lib/view_model/restore/restore_wallet.dart | 4 + .../restore/wallet_restore_from_qr_code.dart | 16 +- lib/view_model/send/send_view_model.dart | 5 +- .../settings/other_settings_view_model.dart | 44 +++-- .../wallet_address_list_view_model.dart | 2 +- .../wallet_hardware_restore_view_model.dart | 39 ++-- lib/view_model/wallet_restore_view_model.dart | 4 +- pubspec_base.yaml | 6 + res/values/strings_ar.arb | 7 +- res/values/strings_bg.arb | 7 +- res/values/strings_cs.arb | 7 +- res/values/strings_de.arb | 15 +- res/values/strings_en.arb | 7 +- res/values/strings_es.arb | 7 +- res/values/strings_fr.arb | 7 +- res/values/strings_ha.arb | 7 +- res/values/strings_hi.arb | 9 +- res/values/strings_hr.arb | 7 +- res/values/strings_hy.arb | 7 +- res/values/strings_id.arb | 7 +- res/values/strings_it.arb | 7 +- res/values/strings_ja.arb | 7 +- res/values/strings_ko.arb | 7 +- res/values/strings_my.arb | 7 +- res/values/strings_nl.arb | 7 +- res/values/strings_pl.arb | 7 +- res/values/strings_pt.arb | 7 +- res/values/strings_ru.arb | 7 +- res/values/strings_th.arb | 7 +- res/values/strings_tl.arb | 7 +- res/values/strings_tr.arb | 7 +- res/values/strings_uk.arb | 7 +- res/values/strings_ur.arb | 7 +- res/values/strings_vi.arb | 7 +- res/values/strings_yo.arb | 7 +- res/values/strings_zh.arb | 7 +- scripts/build_bitbox_flutter.sh | 8 + tool/configure.dart | 44 +++-- 117 files changed, 2048 insertions(+), 723 deletions(-) create mode 100644 assets/images/hardware_wallet/bitbox_man.svg create mode 100644 assets/images/hardware_wallet/coldcard_man.svg create mode 100644 assets/images/hardware_wallet/cupcake_man.svg create mode 100644 assets/images/hardware_wallet/device_bitbox.svg create mode 100644 assets/images/hardware_wallet/device_ledger_nano_x.svg create mode 100644 assets/images/hardware_wallet/device_qr.svg create mode 100644 assets/images/hardware_wallet/foundation_man.svg create mode 100644 assets/images/hardware_wallet/keystone_man.svg create mode 100644 assets/images/hardware_wallet/ledger_man.svg create mode 100644 assets/images/hardware_wallet/seedsigner_man.svg create mode 100644 assets/images/hardware_wallet/trezor_man.svg delete mode 100644 cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart create mode 100644 cw_bitcoin/lib/hardware/bitbox_service.dart create mode 100644 cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart create mode 100644 cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart rename cw_bitcoin/lib/{litecoin_hardware_wallet_service.dart => hardware/litecoin_ledger_service.dart} (74%) create mode 100644 cw_core/lib/hardware/hardware_wallet_service.dart create mode 100644 cw_core/lib/utils/zpub.dart create mode 100644 cw_evm/lib/hardware/evm_bitbox_credentials.dart create mode 100644 cw_evm/lib/hardware/evm_chain_bitbox_service.dart rename cw_evm/lib/{evm_chain_hardware_wallet_service.dart => hardware/evm_chain_ledger_service.dart} (83%) rename cw_evm/lib/{ => hardware}/evm_ledger_credentials.dart (97%) create mode 100644 lib/src/screens/connect_device/select_device_manufacturer_page.dart create mode 100644 lib/src/screens/connect_device/widgets/dotted_divider.dart create mode 100644 lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart create mode 100644 lib/src/screens/dev/qr_tools_page.dart create mode 100644 lib/view_model/dev/qr_tools_view_model.dart create mode 100644 lib/view_model/hardware_wallet/bitbox_view_model.dart create mode 100644 lib/view_model/hardware_wallet/hardware_wallet_view_model.dart create mode 100755 scripts/build_bitbox_flutter.sh diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index b7a645e44..4a6d49d06 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -251,6 +251,14 @@ jobs: ./build_monero_all.sh popd + - name: Build Bitbox Flutter + run: | + set -x -e + pushd scripts + gomobile init; + ./build_bitbox_flutter.sh + popd + - name: Install Flutter dependencies run: | flutter pub get diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 13916001b..dea882410 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -228,6 +228,13 @@ jobs: ./build_monero_all.sh popd + - name: Build Bitbox Flutter + run: | + set -x -e + pushd scripts + ./build_bitbox_flutter.sh --dont-install + popd + - name: Install Flutter dependencies run: | flutter pub get diff --git a/assets/images/hardware_wallet/bitbox_man.svg b/assets/images/hardware_wallet/bitbox_man.svg new file mode 100644 index 000000000..b1123f780 --- /dev/null +++ b/assets/images/hardware_wallet/bitbox_man.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/hardware_wallet/coldcard_man.svg b/assets/images/hardware_wallet/coldcard_man.svg new file mode 100644 index 000000000..73a0658e1 --- /dev/null +++ b/assets/images/hardware_wallet/coldcard_man.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/hardware_wallet/cupcake_man.svg b/assets/images/hardware_wallet/cupcake_man.svg new file mode 100644 index 000000000..841a0c328 --- /dev/null +++ b/assets/images/hardware_wallet/cupcake_man.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/hardware_wallet/device_bitbox.svg b/assets/images/hardware_wallet/device_bitbox.svg new file mode 100644 index 000000000..52368aaec --- /dev/null +++ b/assets/images/hardware_wallet/device_bitbox.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/hardware_wallet/device_ledger_nano_x.svg b/assets/images/hardware_wallet/device_ledger_nano_x.svg new file mode 100644 index 000000000..c605249dc --- /dev/null +++ b/assets/images/hardware_wallet/device_ledger_nano_x.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/hardware_wallet/device_qr.svg b/assets/images/hardware_wallet/device_qr.svg new file mode 100644 index 000000000..06aece5ec --- /dev/null +++ b/assets/images/hardware_wallet/device_qr.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/hardware_wallet/foundation_man.svg b/assets/images/hardware_wallet/foundation_man.svg new file mode 100644 index 000000000..5946c59ff --- /dev/null +++ b/assets/images/hardware_wallet/foundation_man.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/hardware_wallet/keystone_man.svg b/assets/images/hardware_wallet/keystone_man.svg new file mode 100644 index 000000000..e9d7b84e7 --- /dev/null +++ b/assets/images/hardware_wallet/keystone_man.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/hardware_wallet/ledger_man.svg b/assets/images/hardware_wallet/ledger_man.svg new file mode 100644 index 000000000..399a6a367 --- /dev/null +++ b/assets/images/hardware_wallet/ledger_man.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/hardware_wallet/seedsigner_man.svg b/assets/images/hardware_wallet/seedsigner_man.svg new file mode 100644 index 000000000..ec0c75074 --- /dev/null +++ b/assets/images/hardware_wallet/seedsigner_man.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/hardware_wallet/trezor_man.svg b/assets/images/hardware_wallet/trezor_man.svg new file mode 100644 index 000000000..253e18b70 --- /dev/null +++ b/assets/images/hardware_wallet/trezor_man.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart deleted file mode 100644 index c8715b239..000000000 --- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_core/hardware/hardware_account_data.dart'; -import 'package:ledger_bitcoin/ledger_bitcoin.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; -import 'package:cw_core/utils/print_verbose.dart'; - -class BitcoinHardwareWalletService { - BitcoinHardwareWalletService(this.ledgerConnection); - - final LedgerConnection ledgerConnection; - - Future> getAvailableAccounts( - {int index = 0, int limit = 5}) async { - final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection); - - final masterFp = await bitcoinLedgerApp.getMasterFingerprint(); - - final accounts = []; - final indexRange = List.generate(limit, (i) => i + index); - - for (final i in indexRange) { - final derivationPath = "m/84'/0'/$i'"; - final xpub = - await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath); - Bip32Slip10Secp256k1 hd = - Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0)); - - final address = generateP2WPKHAddress( - hd: hd, index: 0, network: BitcoinNetwork.mainnet); - - accounts.add(HardwareAccountData( - address: address, - accountIndex: i, - derivationPath: derivationPath, - masterFingerprint: masterFp, - xpub: xpub, - )); - } - - return accounts; - } -} diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index a5c209e91..241ef344a 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -12,6 +12,7 @@ import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart'; import 'package:cw_bitcoin/payjoin/manager.dart'; import 'package:cw_bitcoin/payjoin/storage.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; @@ -25,7 +26,7 @@ import 'package:cw_core/output_info.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/utils/zpub.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; @@ -241,7 +242,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { return BitcoinWallet( mnemonic: mnemonic, - xpub: keysData.xPub, + xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null, password: password, passphrase: passphrase, walletInfo: walletInfo, @@ -260,16 +261,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { payjoinBox: payjoinBox); } - LedgerConnection? _ledgerConnection; - BitcoinLedgerApp? _bitcoinLedgerApp; - - @override - void setLedgerConnection(LedgerConnection connection) { - _ledgerConnection = connection; - _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!, - derivationPath: walletInfo.derivationInfo!.derivationPath!); - } - @override Future close({bool shouldCleanup = false}) async { payjoinManager.cleanupSessions(); @@ -331,7 +322,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, }) async { - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); + final masterFingerprint = + await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint(); final psbt = await buildPsbt( outputs: outputs, @@ -347,7 +339,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { outputOrdering: outputOrdering, ); - final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt); + final psbtStr = base64Encode(psbt.serialize()); + final rawHex = await hardwareWalletService!.signTransaction(transaction: psbtStr); return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); } @@ -495,8 +488,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; - final signature = await _bitcoinLedgerApp!.signMessage( - message: ascii.encode(message), signDerivationPath: derivationPath); + final signature = await hardwareWalletService! + .signMessage(message: ascii.encode(message), derivationPath: derivationPath); return base64Encode(signature); } diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 3d71a0c39..9be8df388 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -60,6 +60,7 @@ class BitcoinWalletFromKeysCredentials extends WalletCredentials { required String password, required this.xpub, WalletInfo? walletInfo, + super.hardwareWalletType }) : super(name: name, password: password, walletInfo: walletInfo); final String xpub; diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 8d41298ee..e5edfecc2 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -7,6 +7,7 @@ import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/zpub.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart'; @@ -150,9 +151,12 @@ class BitcoinWalletService extends WalletService< credentials.walletInfo?.network = network.value; credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath; + + final xpub = convertZpubToXpub(credentials.hwAccountData.xpub!); + final wallet = await BitcoinWallet( password: credentials.password!, - xpub: credentials.hwAccountData.xpub, + xpub: xpub, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, networkParam: network, @@ -166,13 +170,15 @@ class BitcoinWalletService extends WalletService< @override Future restoreFromKeys(BitcoinWalletFromKeysCredentials credentials, - {bool? isTestnet}) async { + {bool? isTestnet}) async { final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; credentials.walletInfo?.network = network.value; + final xpub = convertZpubToXpub(credentials.xpub); + final wallet = await BitcoinWallet( password: credentials.password!, - xpub: credentials.xpub, + xpub: xpub, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, networkParam: network, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 75f59bb89..38b765941 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/utils/print_verbose.dart'; @@ -75,8 +76,9 @@ abstract class ElectrumWalletBase ElectrumBalance? initialBalance, CryptoCurrency? currency, bool? alwaysScan, - }) : accountHD = - getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), + }) + : accountHD = getAccountHDWallet(currency, network, seedBytes, xpub, + walletInfo.derivationInfo, walletInfo.hardwareWalletType), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], @@ -113,8 +115,13 @@ abstract class ElectrumWalletBase sharedPrefs.complete(SharedPreferences.getInstance()); } - static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network, - Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) { + static Bip32Slip10Secp256k1 getAccountHDWallet( + CryptoCurrency? currency, + BasedUtxoNetwork network, + Uint8List? seedBytes, + String? xpub, + DerivationInfo? derivationInfo, + HardwareWalletType? hardwareWalletType) { if (seedBytes == null && xpub == null) { throw Exception( "To create a Wallet you need either a seed or an xpub. This should not happen"); @@ -125,8 +132,10 @@ abstract class ElectrumWalletBase case CryptoCurrency.btc: case CryptoCurrency.ltc: case CryptoCurrency.tbtc: - return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath( - _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) + return Bip32Slip10Secp256k1.fromSeed( + seedBytes, getKeyNetVersion(network, hardwareWalletType)) + .derivePath( + _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) as Bip32Slip10Secp256k1; case CryptoCurrency.bch: return bitcoinCashHDWallet(seedBytes); @@ -137,7 +146,8 @@ abstract class ElectrumWalletBase } } - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network)); + return Bip32Slip10Secp256k1.fromExtendedKey( + xpub!, getKeyNetVersion(network, hardwareWalletType)); } static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => @@ -149,10 +159,13 @@ abstract class ElectrumWalletBase static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; - static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) { + static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network, + [HardwareWalletType? hardwareWalletType]) { switch (network) { case LitecoinNetwork.mainnet: - return Bip44Conf.litecoinMainNet.altKeyNetVer; + if (hardwareWalletType == HardwareWalletType.ledger) + return Bip44Conf.litecoinMainNet.altKeyNetVer; + return null; default: return null; } @@ -1408,6 +1421,8 @@ abstract class ElectrumWalletBase } } + HardwareWalletService? hardwareWalletService; + void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError(); Future buildHardwareWalletTransaction({ diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart new file mode 100644 index 000000000..99bbf763b --- /dev/null +++ b/cw_bitcoin/lib/hardware/bitbox_service.dart @@ -0,0 +1,93 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; +import 'dart:typed_data'; + +import 'package:bitbox_flutter/bitbox_manager.dart'; +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart'; +import 'package:cw_bitcoin/psbt/v0_deserialize.dart'; +import 'package:cw_bitcoin/psbt/v0_finalizer.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:ledger_bitcoin/psbt.dart'; + +class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService { + BitcoinBitboxService(this.manager); + + final int bitboxCoinType = 0; // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40 + final BitboxManager manager; + + @override + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final accounts = []; + final indexRange = List.generate(limit, (i) => i + index); + + for (final i in indexRange) { + final derivationPath = "m/84'/0'/$i'"; + final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, 1); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0)); + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); + + accounts.add(HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + xpub: xPub, + )); + } + + return accounts; + } + + + @override + Future signTransaction({required String transaction}) async { + final psbt = PsbtV2()..deserialize(base64Decode(transaction)); + log(base64Encode(psbt.asPsbtV0()), name: 'PSBT'); + final signedPsbt = await manager.signBTCPsbt(bitboxCoinType, base64Encode(psbt.asPsbtV0())); + log(signedPsbt, name: 'signed PSBT'); + final transactionRes = PsbtV2() + ..deserializeV0(base64Decode(signedPsbt)) + ..finalizeV0(); + return transactionRes.extract(); + } + + @override + Future signMessage({required Uint8List message, String? derivationPath}) async { + throw UnimplementedError(); // ToDo (Konsti) + } + + @override + Future getMasterFingerprint() => manager.getMasterFingerprint(); +} + +class LitecoinBitboxService extends HardwareWalletService { + LitecoinBitboxService(this.manager); + + final BitboxManager manager; + + @override + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final accounts = []; + final indexRange = List.generate(limit, (i) => i + index); + + for (final i in indexRange) { + final derivationPath = "m/84'/2'/$i'"; + final xPub = await manager.getBTCXPub(0, derivationPath, 1); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0)); + final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); + + accounts.add(HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + xpub: xPub, + )); + } + + return accounts; + } +} diff --git a/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart new file mode 100644 index 000000000..cc4e2092d --- /dev/null +++ b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart @@ -0,0 +1,5 @@ +import 'dart:typed_data'; + +mixin BitcoinHardwareWalletService { + Future getMasterFingerprint(); +} diff --git a/cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart b/cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart new file mode 100644 index 000000000..65ab76400 --- /dev/null +++ b/cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart @@ -0,0 +1,57 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:ledger_bitcoin/ledger_bitcoin.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; + +class BitcoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService { + BitcoinLedgerService(this.ledgerConnection) + : bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection); + + final LedgerConnection ledgerConnection; + final BitcoinLedgerApp bitcoinLedgerApp; + + @override + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final masterFp = await bitcoinLedgerApp.getMasterFingerprint(); + + final accounts = []; + final indexRange = List.generate(limit, (i) => i + index); + + for (final i in indexRange) { + final derivationPath = "m/84'/0'/$i'"; + final xPub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0)); + + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); + + accounts.add(HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + masterFingerprint: masterFp, + xpub: xPub, + )); + } + + return accounts; + } + + @override + Future signTransaction({required String transaction}) => + bitcoinLedgerApp.signPsbt(psbt: PsbtV2()..deserialize(base64Decode(transaction))); + + @override + Future getMasterFingerprint() => bitcoinLedgerApp.getMasterFingerprint(); + + @override + Future signMessage({required Uint8List message, String? derivationPath}) => + bitcoinLedgerApp.signMessage(message: message, signDerivationPath: derivationPath); +} diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart similarity index 74% rename from cw_bitcoin/lib/litecoin_hardware_wallet_service.dart rename to cw_bitcoin/lib/hardware/litecoin_ledger_service.dart index 62840933c..8334eb879 100644 --- a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart @@ -4,16 +4,17 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; -class LitecoinHardwareWalletService { - LitecoinHardwareWalletService(this.ledgerConnection); +class LitecoinLedgerService extends HardwareWalletService { + LitecoinLedgerService(this.ledgerConnection); final LedgerConnection ledgerConnection; - Future> getAvailableAccounts( - {int index = 0, int limit = 5}) async { + @override + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection); await litecoinLedgerApp.getVersion(); @@ -27,11 +28,9 @@ class LitecoinHardwareWalletService { final xpub = await litecoinLedgerApp.getXPubKey( accountsDerivationPath: derivationPath, xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16)); - final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion) - .childKey(Bip32KeyIndex(0)); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0)); - final address = generateP2WPKHAddress( - hd: hd, index: 0, network: LitecoinNetwork.mainnet); + final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); accounts.add(HardwareAccountData( address: address, diff --git a/cw_bitcoin/lib/psbt/transaction_builder.dart b/cw_bitcoin/lib/psbt/transaction_builder.dart index 4692e0906..7d2badeda 100644 --- a/cw_bitcoin/lib/psbt/transaction_builder.dart +++ b/cw_bitcoin/lib/psbt/transaction_builder.dart @@ -52,7 +52,9 @@ class PSBTTransactionBuild { .map((e) => e?.toLowerCase()) .contains(output.address.toAddress().toLowerCase())) .firstOrNull; - if (cwOutput != null) { + if (cwOutput != null && + cwOutput.extra.containsKey('bip353_name') && + cwOutput.extra.containsKey('bip353_proof')) { final bip353Name = utf8.encode(cwOutput.extra['bip353_name'] as String); final bip353Proof = base64.decode(cwOutput.extra['bip353_proof'] as String); diff --git a/cw_bitcoin/lib/utils.dart b/cw_bitcoin/lib/utils.dart index a7435bed1..1eadc049b 100644 --- a/cw_bitcoin/lib/utils.dart +++ b/cw_bitcoin/lib/utils.dart @@ -1,3 +1,4 @@ +import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; @@ -51,4 +52,4 @@ String generateP2TRAddress({ }) => ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) .toTaprootAddress() - .toAddress(network); + .toAddress(network); \ No newline at end of file diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 53db8c928..6f0580bac 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -89,6 +89,13 @@ packages: url: "https://github.com/cake-tech/bitbox-flutter.git" source: git version: "1.0.1" + bitbox_flutter: + dependency: "direct main" + description: + path: "../scripts/bitbox_flutter" + relative: true + source: path + version: "0.0.1" bitcoin_base: dependency: "direct overridden" description: @@ -597,8 +604,8 @@ packages: dependency: "direct main" description: path: "packages/ledger-bitcoin" - ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 - resolved-ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 + ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3" + resolved-ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3" url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" source: git version: "0.0.3" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 0c530671c..5bb0b17de 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -49,11 +49,13 @@ dependencies: git: url: https://github.com/cake-tech/ledger-flutter-plus-plugins path: packages/ledger-bitcoin - ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 + ref: 6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3 ledger_litecoin: git: url: https://github.com/cake-tech/ledger-flutter-plus-plugins path: packages/ledger-litecoin + bitbox_flutter: + path: ../scripts/bitbox_flutter socks_socket: git: url: https://github.com/sneurlax/socks_socket diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart index 76a501af1..92c809637 100644 --- a/cw_core/lib/hardware/device_connection_type.dart +++ b/cw_core/lib/hardware/device_connection_type.dart @@ -1,22 +1,39 @@ +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; enum DeviceConnectionType { usb, ble; - static List supportedConnectionTypes(WalletType walletType, + static List supportedConnectionTypes( + WalletType walletType, HardwareWalletType hardwareType, [bool isIOS = false]) { - switch (walletType) { - case WalletType.monero: - case WalletType.bitcoin: - case WalletType.litecoin: - case WalletType.ethereum: - case WalletType.polygon: - if (isIOS) return [DeviceConnectionType.ble]; - return [DeviceConnectionType.ble, DeviceConnectionType.usb]; - default: - return []; + bool isSupported = false; + switch (hardwareType) { + case HardwareWalletType.bitbox: + isSupported = [ + WalletType.bitcoin, + // WalletType.litecoin, + WalletType.ethereum, + WalletType.polygon + ].contains(walletType); + break; + case HardwareWalletType.ledger: + isSupported = [ + WalletType.monero, + WalletType.bitcoin, + WalletType.litecoin, + WalletType.ethereum, + WalletType.polygon + ].contains(walletType); + break; } + + return isSupported + ? (isIOS + ? [DeviceConnectionType.ble] + : [DeviceConnectionType.ble, DeviceConnectionType.usb]) + : []; } String get iconString { diff --git a/cw_core/lib/hardware/hardware_wallet_service.dart b/cw_core/lib/hardware/hardware_wallet_service.dart new file mode 100644 index 000000000..19125ba84 --- /dev/null +++ b/cw_core/lib/hardware/hardware_wallet_service.dart @@ -0,0 +1,13 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:cw_core/hardware/hardware_account_data.dart'; + +abstract class HardwareWalletService { + Future> getAvailableAccounts({int index = 0, int limit = 5}); + + Future signTransaction({required String transaction}) => throw UnimplementedError(); + + Future signMessage({required Uint8List message, String? derivationPath}) => + throw UnimplementedError(); +} diff --git a/cw_core/lib/utils/zpub.dart b/cw_core/lib/utils/zpub.dart new file mode 100644 index 000000000..90f03895f --- /dev/null +++ b/cw_core/lib/utils/zpub.dart @@ -0,0 +1,46 @@ + +import 'dart:typed_data'; + +import 'package:blockchain_utils/blockchain_utils.dart'; + +String convertZpubToXpub(String zpub) { + try { + final decoded = Base58Decoder.checkDecode(zpub); + + if (decoded.length < 4) { + throw ArgumentError('Invalid extended public key length'); + } + + final versionBytes = decoded.sublist(0, 4); + final zpubVersionBytes = [0x04, 0xb2, 0x47, 0x46]; // zpub mainnet version + final zpubTestnetVersionBytes = [0x04, 0x5f, 0x1c, 0xf6]; // vpub testnet version + + bool isZpub = listEquals(versionBytes, zpubVersionBytes); + bool isVpub = listEquals(versionBytes, zpubTestnetVersionBytes); + + if (!isZpub && !isVpub) { + return zpub; + } + + final xpubVersionBytes = isZpub ? + [0x04, 0x88, 0xb2, 0x1e] : // xpub mainnet + [0x04, 0x35, 0x87, 0xcf]; // tpub testnet + + final newExtendedKey = Uint8List.fromList([ + ...xpubVersionBytes, + ...decoded.sublist(4), + ]); + + return Base58Encoder.checkEncode(newExtendedKey); + } catch (e) { + throw ArgumentError('Failed to convert zpub to xpub: $e'); + } +} + +bool listEquals(List a, List b) { + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + return true; +} diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 7d0fc4f04..b492fb041 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -62,6 +62,8 @@ abstract class WalletBase walletInfo.isHardwareWallet; + HardwareWalletType? get hardwareWalletType => walletInfo.hardwareWalletType; + bool get hasRescan => false; Future connectToNode({required Node node}); diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index d62e61941..15a9438c0 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -25,6 +25,16 @@ enum DerivationType { enum HardwareWalletType { @HiveField(0) ledger, + @HiveField(1) + bitbox, + @HiveField(2) + cupcake, + @HiveField(3) + coldcard, + @HiveField(4) + seedsigner, + @HiveField(5) + keystone, } @HiveType(typeId: DerivationInfo.typeId) @@ -224,7 +234,8 @@ class WalletInfo extends HiveObject { return showIntroCakePayCard!; } - bool get isHardwareWallet => hardwareWalletType != null; + bool get isHardwareWallet => + [HardwareWalletType.ledger, HardwareWalletType.bitbox].contains(hardwareWalletType); DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp); diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index fcf747d9b..2cfa93590 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -47,7 +47,7 @@ packages: source: hosted version: "2.13.0" blockchain_utils: - dependency: transitive + dependency: "direct main" description: path: "." ref: cake-update-v2 diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index ed7a6e488..ed1f90c9f 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -40,6 +40,10 @@ dependencies: ref: e6232c53c1595469931ababa878759a067c02e94 torch_dart: path: ../scripts/torch_dart + blockchain_utils: + git: + url: https://github.com/cake-tech/blockchain_utils + ref: cake-update-v2 dev_dependencies: flutter_test: diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index 29a88dcce..215e8bc28 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -9,6 +9,7 @@ import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart'; import 'package:cw_evm/pending_evm_chain_transaction.dart'; import 'package:cw_evm/.secrets.g.dart' as secrets; import 'package:flutter/foundation.dart'; diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index f7fb244bb..db35030c9 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -29,7 +29,8 @@ import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_wallet_addresses.dart'; -import 'package:cw_evm/evm_ledger_credentials.dart'; +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart'; +import 'package:cw_evm/hardware/evm_ledger_credentials.dart'; import 'package:hex/hex.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -207,9 +208,12 @@ abstract class EVMChainWalletBase // check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯ await _checkForExistingScamTokens(); - if (walletInfo.isHardwareWallet) { + if (walletInfo.hardwareWalletType == HardwareWalletType.ledger) { _evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address); walletAddresses.address = walletInfo.address; + } else if (walletInfo.hardwareWalletType == HardwareWalletType.bitbox) { + _evmChainPrivateKey = EvmBitboxCredentials(walletInfo.address); + walletAddresses.address = walletInfo.address; } else { _evmChainPrivateKey = await getPrivateKey( mnemonic: _mnemonic, @@ -547,7 +551,8 @@ abstract class EVMChainWalletBase } } - if (transactionCurrency is Erc20Token && isHardwareWallet) { + if (transactionCurrency is Erc20Token && + walletInfo.hardwareWalletType == HardwareWalletType.ledger) { await (_evmChainPrivateKey as EvmLedgerCredentials) .provideERC20Info(transactionCurrency.contractAddress, _client.chainId); } diff --git a/cw_evm/lib/hardware/evm_bitbox_credentials.dart b/cw_evm/lib/hardware/evm_bitbox_credentials.dart new file mode 100644 index 000000000..d8dca4ea4 --- /dev/null +++ b/cw_evm/lib/hardware/evm_bitbox_credentials.dart @@ -0,0 +1,79 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:bitbox_flutter/bitbox_manager.dart'; +import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception; +import 'package:web3dart/crypto.dart'; +import 'package:web3dart/web3dart.dart'; + +class EvmBitboxCredentials extends CredentialsWithKnownAddress { + final String _address; + + BitboxManager? bitboxManager; + String? derivationPath; + + EvmBitboxCredentials(this._address); + + @override + EthereumAddress get address => EthereumAddress.fromHex(_address); + + void setBitbox(BitboxManager connection, [String? derivationPath_]) { + bitboxManager = connection; + derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0"; + } + + @override + MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) => + throw UnimplementedError("EvmLedgerCredentials.signToEcSignature"); + + @override + Future signToSignature(Uint8List payload, + {int? chainId, bool isEIP1559 = false}) async { + if (bitboxManager == null) { + throw exception.DeviceNotConnectedException(); + } + + if (isEIP1559) payload = payload.sublist(1); + final sig = await bitboxManager! + .signETHRLPTransaction(chainId ?? 1, derivationPath!, bytesToHex(payload), isEIP1559); + + final r = bytesToHex(sig.sublist(0, 32)); + final s = bytesToHex(sig.sublist(32, 32 + 32)); + final v = sig.last.toInt(); + + if (isEIP1559) { + return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), v); + } + + var truncChainId = chainId ?? 1; + while (truncChainId.bitLength > 32) { + truncChainId >>= 8; + } + + final truncTarget = truncChainId * 2 + 35; + + int parity = v; + if (truncTarget & 0xff == v) { + parity = 0; + } else if ((truncTarget + 1) & 0xff == v) { + parity = 1; + } + + // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26 + final chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity; + + return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV); + } + + @override + Future signPersonalMessage(Uint8List payload, {int? chainId}) async { + if (isNotConnected) throw exception.DeviceNotConnectedException(); + return await bitboxManager!.signETHMessage(chainId ?? 1, derivationPath!, payload); + } + + @override + Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) => + throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List"); + + bool get isNotConnected => bitboxManager == null; +} diff --git a/cw_evm/lib/hardware/evm_chain_bitbox_service.dart b/cw_evm/lib/hardware/evm_chain_bitbox_service.dart new file mode 100644 index 000000000..ef1918b09 --- /dev/null +++ b/cw_evm/lib/hardware/evm_chain_bitbox_service.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:bitbox_flutter/bitbox_manager.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; + +class EVMChainBitboxService extends HardwareWalletService { + EVMChainBitboxService(this.manager, {this.chainId = 1}); + + final BitboxManager manager; + final int chainId; + + @override + Future> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final accounts = []; + final indexRange = List.generate(limit, (i) => i + index); + + for (final i in indexRange) { + final derivationPath = "m/44'/60'/0'/0/$i"; + final address = await manager.getETHAddress(chainId, derivationPath); + + accounts.add(HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + )); + } + + return accounts; + } +} diff --git a/cw_evm/lib/evm_chain_hardware_wallet_service.dart b/cw_evm/lib/hardware/evm_chain_ledger_service.dart similarity index 83% rename from cw_evm/lib/evm_chain_hardware_wallet_service.dart rename to cw_evm/lib/hardware/evm_chain_ledger_service.dart index d8f67c641..d1c883f3b 100644 --- a/cw_evm/lib/evm_chain_hardware_wallet_service.dart +++ b/cw_evm/lib/hardware/evm_chain_ledger_service.dart @@ -1,14 +1,16 @@ import 'dart:async'; import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:ledger_ethereum/ledger_ethereum.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; -class EVMChainHardwareWalletService { - EVMChainHardwareWalletService(this.ledgerConnection); +class EVMChainLedgerService extends HardwareWalletService { + EVMChainLedgerService(this.ledgerConnection); final LedgerConnection ledgerConnection; + @override Future> getAvailableAccounts( {int index = 0, int limit = 5}) async { final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection); diff --git a/cw_evm/lib/evm_ledger_credentials.dart b/cw_evm/lib/hardware/evm_ledger_credentials.dart similarity index 97% rename from cw_evm/lib/evm_ledger_credentials.dart rename to cw_evm/lib/hardware/evm_ledger_credentials.dart index b579b9108..13a93cfb2 100644 --- a/cw_evm/lib/evm_ledger_credentials.dart +++ b/cw_evm/lib/hardware/evm_ledger_credentials.dart @@ -76,6 +76,8 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { final sig = await ethereumLedgerApp!.signMessage(payload); + if (sig.isEmpty) throw Exception("No Signature received from device!"); + final r = sig.sublist(1, 1 + 32); final s = sig.sublist(1 + 32, 1 + 32 + 32); final v = [sig[0]]; diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index 80788ca06..60e214da9 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -31,6 +31,8 @@ dependencies: url: https://github.com/cake-tech/ledger-flutter-plus-plugins path: packages/ledger-ethereum ref: f4761cd5171d4c1e2e42fd3298261650539fb2db + bitbox_flutter: + path: ../scripts/bitbox_flutter dependency_overrides: web3dart: diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 914d52f6b..e04f73f9d 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -66,11 +66,11 @@ packages: dependency: transitive description: path: "." - ref: cake-update-v4 - resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a" + ref: cake-update-v2 + resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57" url: "https://github.com/cake-tech/blockchain_utils" source: git - version: "4.3.0" + version: "3.3.0" bluez: dependency: transitive description: @@ -599,10 +599,10 @@ packages: description: path: "." ref: cake-update-v2 - resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" + resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273" url: "https://github.com/cake-tech/on_chain.git" source: git - version: "6.2.0" + version: "3.7.0" package_config: dependency: transitive description: diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 12493fed3..91877fc70 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -252,6 +252,13 @@ SPEC CHECKSUMS: package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58 + SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a + sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089 SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index e8c2559b5..953c88c27 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -23,8 +23,10 @@ class CWBitcoin extends Bitcoin { required String name, required String password, required String xpub, + HardwareWalletType? hardwareWalletType, }) => - BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub); + BitcoinWalletFromKeysCredentials( + name: name, password: password, xpub: xpub, hardwareWalletType: hardwareWalletType); @override WalletCredentials createBitcoinRestoreWalletFromWIFCredentials( @@ -315,8 +317,12 @@ class CWBitcoin extends Bitcoin { } @override - List getLitecoinReceivePageOptions() { - if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { + List getLitecoinReceivePageOptions(Object wallet) { + final litecoinWallet = wallet as ElectrumWallet; + if (Platform.isLinux || + Platform.isMacOS || + Platform.isWindows || + litecoinWallet.isHardwareWallet) { return BitcoinReceivePageOption.allLitecoin .where((element) => element != BitcoinReceivePageOption.mweb) .toList(); @@ -536,27 +542,22 @@ class CWBitcoin extends Bitcoin { } @override - Future> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, - {int index = 0, int limit = 5}) async { - final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection); - try { - return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } catch (err) { - printV(err); - throw err; - } + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) { + (wallet as ElectrumWallet).hardwareWalletService = service; } @override - Future> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, - {int index = 0, int limit = 5}) async { - final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection); - try { - return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } catch (err) { - printV(err); - throw err; - } + HardwareWalletService getLedgerHardwareWalletService( + ledger.LedgerConnection connection, bool isBitcoin) { + if (isBitcoin) return BitcoinLedgerService(connection); + return LitecoinLedgerService(connection); + } + + @override + HardwareWalletService getBitboxHardwareWalletService( + bitbox.BitboxManager manager, bool isBitcoin) { + if (isBitcoin) return BitcoinBitboxService(manager); + return LitecoinBitboxService(manager); } @override diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart index 689c86fea..7cc92eb9b 100644 --- a/lib/buy/buy_provider.dart +++ b/lib/buy/buy_provider.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/order/order.dart'; import 'package:cake_wallet/buy/pairs_utils.dart'; import 'package:cake_wallet/buy/payment_method.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; @@ -13,14 +13,14 @@ abstract class BuyProvider { BuyProvider({ required this.wallet, required this.isTestEnvironment, - required this.ledgerVM, + required this.hardwareWalletVM, required this.supportedCryptoList, required this.supportedFiatList }); final WalletBase wallet; final bool isTestEnvironment; - final LedgerViewModel? ledgerVM; + final HardwareWalletViewModel? hardwareWalletVM; final List> supportedCryptoList; final List> supportedFiatList; diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 3ab5d4273..c88ea37c9 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -10,8 +10,8 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; @@ -24,16 +24,16 @@ class DFXBuyProvider extends BuyProvider { DFXBuyProvider({ required WalletBase wallet, bool isTestEnvironment = false, - LedgerViewModel? ledgerVM, + HardwareWalletViewModel? hardwareWalletVM, }) : super( - wallet: wallet, - isTestEnvironment: isTestEnvironment, - ledgerVM: ledgerVM, - supportedCryptoList: supportedCryptoToFiatPairs( - notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), - supportedFiatList: supportedFiatToCryptoPairs( - notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto) - ); + wallet: wallet, + isTestEnvironment: isTestEnvironment, + hardwareWalletVM: hardwareWalletVM, + supportedCryptoList: supportedCryptoToFiatPairs( + notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), + supportedFiatList: supportedFiatToCryptoPairs( + notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto), + ); static const _baseUrl = 'api.dfx.swiss'; @@ -104,8 +104,7 @@ class DFXBuyProvider extends BuyProvider { // } Future auth(String walletAddress) async { - final signMessage = await getSignature( - await getSignMessage(walletAddress), walletAddress); + final signMessage = await getSignature(await getSignMessage(walletAddress), walletAddress); final requestBody = jsonEncode({ 'wallet': walletName, @@ -119,7 +118,6 @@ class DFXBuyProvider extends BuyProvider { headers: {'Content-Type': 'application/json'}, body: requestBody, ); - if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); @@ -139,14 +137,13 @@ class DFXBuyProvider extends BuyProvider { case WalletType.polygon: case WalletType.solana: case WalletType.tron: - final r = await wallet.signMessage(message); - return r; + return wallet.signMessage(message); case WalletType.monero: case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: case WalletType.zano: - return await wallet.signMessage(message, address: walletAddress); + return wallet.signMessage(message, address: walletAddress); default: throw Exception("WalletType is not available for DFX ${wallet.type}"); } @@ -336,16 +333,17 @@ class DFXBuyProvider extends BuyProvider { required String cryptoCurrencyAddress, String? countryCode}) async { if (wallet.isHardwareWallet) { - if (!ledgerVM!.isConnected) { + if (!hardwareWalletVM!.isConnected) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: wallet.walletInfo.type, - onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) { - ledgerVM.setLedger(wallet); + hardwareWalletType: wallet.walletInfo.hardwareWalletType!, + onConnectDevice: (context, hwwVM) { + hwwVM.initWallet(wallet); Navigator.of(context).pop(); })); } else { - ledgerVM!.setLedger(wallet); + hardwareWalletVM!.initWallet(wallet); } } @@ -370,14 +368,13 @@ class DFXBuyProvider extends BuyProvider { } } catch (e) { await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: "DFX.swiss", - alertContent: S.of(context).buy_provider_unavailable + ': $e', - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + context: context, + builder: (context) => AlertWithOneAction( + alertTitle: "DFX.swiss", + alertContent: '${S.of(context).buy_provider_unavailable}: $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()), + ); } } diff --git a/lib/buy/kryptonim/kryptonim.dart b/lib/buy/kryptonim/kryptonim.dart index 1d2d10f58..d79638119 100644 --- a/lib/buy/kryptonim/kryptonim.dart +++ b/lib/buy/kryptonim/kryptonim.dart @@ -20,7 +20,7 @@ class KryptonimBuyProvider extends BuyProvider { : super( wallet: wallet, isTestEnvironment: isTestEnvironment, - ledgerVM: null, + hardwareWalletVM: null, supportedCryptoList: supportedCryptoToFiatPairs( notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), supportedFiatList: supportedFiatToCryptoPairs( diff --git a/lib/buy/meld/meld_buy_provider.dart b/lib/buy/meld/meld_buy_provider.dart index 002ab0465..632d08e55 100644 --- a/lib/buy/meld/meld_buy_provider.dart +++ b/lib/buy/meld/meld_buy_provider.dart @@ -22,7 +22,7 @@ class MeldBuyProvider extends BuyProvider { : super( wallet: wallet, isTestEnvironment: isTestEnvironment, - ledgerVM: null, + hardwareWalletVM: null, supportedCryptoList: supportedCryptoToFiatPairs( notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), supportedFiatList: supportedFiatToCryptoPairs( diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index 92f9e5046..8e3304446 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -36,7 +36,7 @@ class MoonPayProvider extends BuyProvider { super( wallet: wallet, isTestEnvironment: isTestEnvironment, - ledgerVM: null, + hardwareWalletVM: null, supportedCryptoList: supportedCryptoToFiatPairs( notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), supportedFiatList: supportedFiatToCryptoPairs( diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 80c9a6326..1d1d02926 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -9,7 +9,6 @@ import 'package:cake_wallet/buy/payment_method.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/themes/core/theme_store.dart'; -import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; @@ -22,7 +21,7 @@ class OnRamperBuyProvider extends BuyProvider { {required WalletBase wallet, bool isTestEnvironment = false}) : super(wallet: wallet, isTestEnvironment: isTestEnvironment, - ledgerVM: null, + hardwareWalletVM: null, supportedCryptoList: supportedCryptoToFiatPairs( notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), supportedFiatList: supportedFiatToCryptoPairs( diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 4b9c1aa70..50f7f86d7 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -11,9 +11,9 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; @@ -22,16 +22,19 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; class RobinhoodBuyProvider extends BuyProvider { - RobinhoodBuyProvider( - {required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM}) - : super( - wallet: wallet, - isTestEnvironment: isTestEnvironment, - ledgerVM: ledgerVM, - supportedCryptoList: supportedCryptoToFiatPairs( - notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), - supportedFiatList: supportedFiatToCryptoPairs( - notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto)); + RobinhoodBuyProvider({ + required WalletBase wallet, + bool isTestEnvironment = false, + HardwareWalletViewModel? hardwareWalletVM, + }) : super( + wallet: wallet, + isTestEnvironment: isTestEnvironment, + hardwareWalletVM: hardwareWalletVM, + supportedCryptoList: supportedCryptoToFiatPairs( + notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), + supportedFiatList: supportedFiatToCryptoPairs( + notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto), + ); static const _baseUrl = 'applink.robinhood.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; @@ -69,8 +72,8 @@ class RobinhoodBuyProvider extends BuyProvider { final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId}); try { - final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'}); - + final response = + await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'}); if (response.statusCode == 200) { final responseData = jsonDecode(response.body) as Map; @@ -104,11 +107,11 @@ class RobinhoodBuyProvider extends BuyProvider { case WalletType.polygon: case WalletType.solana: case WalletType.tron: - return await wallet.signMessage(message); + return wallet.signMessage(message); case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return await wallet.signMessage(message, address: wallet.walletAddresses.address); + return wallet.signMessage(message, address: wallet.walletAddresses.address); default: throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood"); } @@ -123,14 +126,14 @@ class RobinhoodBuyProvider extends BuyProvider { final uri = Uri.https(_cIdBaseUrl, "/api/robinhood"); - var response = await ProxyWrapper().post( + final response = await ProxyWrapper().post( clearnetUri: uri, headers: {'Content-Type': 'application/json'}, - body: json.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}), + body: json + .encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}), ); if (response.statusCode == 200) { - return (jsonDecode(response.body) as Map)['connectId'] as String; } else { throw Exception('Provider currently unavailable. Status: ${response.statusCode}'); @@ -158,16 +161,17 @@ class RobinhoodBuyProvider extends BuyProvider { required String cryptoCurrencyAddress, String? countryCode}) async { if (wallet.isHardwareWallet) { - if (!ledgerVM!.isConnected) { + if (!hardwareWalletVM!.isConnected) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: wallet.walletInfo.type, - onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) { - ledgerVM.setLedger(wallet); + hardwareWalletType: wallet.walletInfo.hardwareWalletType!, + onConnectDevice: (context, hwwVM) { + hwwVM.initWallet(wallet); Navigator.of(context).pop(); })); } else { - ledgerVM!.setLedger(wallet); + hardwareWalletVM!.initWallet(wallet); } } @@ -176,14 +180,13 @@ class RobinhoodBuyProvider extends BuyProvider { await launchUrl(uri, mode: LaunchMode.externalApplication); } catch (e) { await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: "Robinhood Connect", - alertContent: e.toString(), - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + context: context, + builder: (context) => AlertWithOneAction( + alertTitle: "Robinhood Connect", + alertContent: '${S.of(context).buy_provider_unavailable}: $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()), + ); } } diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart index f85c04406..a0cf1ed7c 100644 --- a/lib/buy/wyre/wyre_buy_provider.dart +++ b/lib/buy/wyre/wyre_buy_provider.dart @@ -20,7 +20,7 @@ class WyreBuyProvider extends BuyProvider { super( wallet: wallet, isTestEnvironment: isTestEnvironment, - ledgerVM: null, + hardwareWalletVM: null, supportedCryptoList: supportedCryptoToFiatPairs( notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), supportedFiatList: supportedFiatToCryptoPairs( diff --git a/lib/core/new_wallet_type_arguments.dart b/lib/core/new_wallet_type_arguments.dart index b43617835..a2b510298 100644 --- a/lib/core/new_wallet_type_arguments.dart +++ b/lib/core/new_wallet_type_arguments.dart @@ -1,14 +1,15 @@ +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; class NewWalletTypeArguments { final void Function(BuildContext, WalletType)? onTypeSelected; final bool isCreate; - final bool isHardwareWallet; + final HardwareWalletType? hardwareWalletType; NewWalletTypeArguments({ required this.onTypeSelected, required this.isCreate, - required this.isHardwareWallet, + this.hardwareWalletType, }); } diff --git a/lib/di.dart b/lib/di.dart index e00bc5c65..61d478a59 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/dev/network_requests.dart'; +import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart'; import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart'; import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; @@ -53,6 +54,8 @@ import 'package:cake_wallet/themes/core/theme_store.dart'; import 'package:cake_wallet/view_model/dev/monero_background_sync.dart'; import 'package:cake_wallet/view_model/dev/secure_preferences.dart'; import 'package:cake_wallet/view_model/dev/shared_preferences.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; @@ -393,8 +396,22 @@ Future setup({ AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource)); getIt.registerSingleton(SeedSettingsStore()); + getIt.registerFactoryParam((type, _) { + switch(type) { + case HardwareWalletType.bitbox: return getIt(); + case HardwareWalletType.ledger: return getIt(); + case HardwareWalletType.cupcake: + case HardwareWalletType.coldcard: + case HardwareWalletType.seedsigner: + case HardwareWalletType.keystone: + throw Exception("This should not have happened, because airgapped Wallets don't need View Models"); + } + }); + getIt.registerLazySingleton(() => LedgerViewModel()); + getIt.registerLazySingleton(() => BitboxViewModel()); + final secretStore = await SecretStoreBase.load(getIt.get()); getIt.registerSingleton(secretStore); @@ -505,9 +522,9 @@ Future setup({ walletType: args.walletType ?? currentWalletType); }); - getIt.registerFactoryParam((type, _) => - WalletHardwareRestoreViewModel( - getIt.get(), + getIt.registerFactoryParam( + (type, hardwareWalletVM) => WalletHardwareRestoreViewModel( + hardwareWalletVM, getIt.get(), getIt.get(param1: type), _walletInfoSource, @@ -816,7 +833,10 @@ Future setup({ getIt.get(), getIt.get(), _transactionDescriptionBox, - getIt.get().wallet!.isHardwareWallet ? getIt.get() : null, + getIt.get().wallet!.isHardwareWallet + ? getIt( + param1: getIt.get().wallet!.hardwareWalletType!) + : null, coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb, getIt.get(param1: coinTypeToSpendFrom), getIt.get(), @@ -1077,14 +1097,18 @@ Future setup({ isSelected: isSelected)); getIt.registerFactory(() => RobinhoodBuyProvider( - wallet: getIt.get().wallet!, - ledgerVM: - getIt.get().wallet!.isHardwareWallet ? getIt.get() : null)); + wallet: getIt.get().wallet!, + hardwareWalletVM: getIt().wallet!.isHardwareWallet + ? getIt(param1: getIt().wallet!.hardwareWalletType!) + : null, + )); getIt.registerFactory(() => DFXBuyProvider( - wallet: getIt.get().wallet!, - ledgerVM: - getIt.get().wallet!.isHardwareWallet ? getIt.get() : null)); + wallet: getIt().wallet!, + hardwareWalletVM: getIt().wallet!.isHardwareWallet + ? getIt(param1: getIt().wallet!.hardwareWalletType!) + : null, + )); getIt.registerFactory(() => MoonPayProvider( appStore: getIt.get(), @@ -1235,19 +1259,25 @@ Future setup({ getIt.registerFactory(() => FaqPage(getIt.get())); - getIt.registerFactoryParam((WalletType type, - restoredWallet) => - WalletRestoreViewModel(getIt.get(), getIt.get(param1: type), - _walletInfoSource, getIt.get(), - type: type, restoredWallet: restoredWallet)); + getIt.registerFactoryParam?>( + (type, additionalParams) { + final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?; + final hardwareWalletType = additionalParams?['hardwareWalletType'] as HardwareWalletType?; - getIt.registerFactoryParam((WalletType type, - restoredWallet) { - return WalletRestorePage( - getIt.get(param1: type, param2: restoredWallet), - getIt.get()); + return WalletRestoreViewModel( + getIt.get(), + getIt.get(param1: type), + _walletInfoSource, + getIt.get(), + type: type, + restoredWallet: restoredWallet, + hardwareWalletType: hardwareWalletType); }); + getIt.registerFactoryParam?>((type, additionalParams) => + WalletRestorePage(getIt.get(param1: type, param2: additionalParams), + getIt.get())); + getIt.registerFactoryParam, void>( (derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations)); @@ -1565,9 +1595,12 @@ Future setup({ getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get())); getIt.registerFactory(() => SocketHealthLogsViewModel()); + getIt.registerFactory(() => DevSocketHealthLogsPage(getIt.get())); getIt.registerFactory(() => DevNetworkRequests()); + + getIt.registerFactory(() => DevQRToolsPage()); getIt.registerFactory(() => ExchangeProviderLogsViewModel()); getIt.registerFactory(() => DevExchangeProviderLogsPage(getIt.get())); diff --git a/lib/entities/hardware_wallet/hardware_wallet_device.dart b/lib/entities/hardware_wallet/hardware_wallet_device.dart index d3acc5d32..5bfbf96c1 100644 --- a/lib/entities/hardware_wallet/hardware_wallet_device.dart +++ b/lib/entities/hardware_wallet/hardware_wallet_device.dart @@ -1,22 +1,44 @@ +import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; -class HardwareWalletDevice { - final String name; - final HardwareWalletDeviceType type; - final HardwareWalletConnectionType connectionType; +abstract class HardwareWalletDevice { + String get name; - const HardwareWalletDevice({ - required this.name, - required this.type, - required this.connectionType, - }); + HardwareWalletDeviceType get type; - factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) => - HardwareWalletDevice( - name: device.name, - type: device.deviceInfo.toGeneric(), - connectionType: device.connectionType.toGeneric(), - ); + HardwareWalletConnectionType get connectionType; +} + +class LedgerHardwareWalletDevice extends HardwareWalletDevice { + final ledger.LedgerDevice device; + + LedgerHardwareWalletDevice(this.device); + + @override + String get name => device.name; + + @override + HardwareWalletDeviceType get type => device.deviceInfo.toGeneric(); + + @override + HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric(); +} + +class BitboxHardwareWalletDevice extends HardwareWalletDevice { + final bitbox.BitboxDevice device; + + BitboxHardwareWalletDevice(this.device); + + @override + String get name => device.productName; + + @override + HardwareWalletDeviceType get type => name.contains("BitBox02 Nova") + ? HardwareWalletDeviceType.BitBox02Nova + : HardwareWalletDeviceType.BitBox02; + + @override + HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb; } enum HardwareWalletDeviceType { @@ -25,13 +47,16 @@ enum HardwareWalletDeviceType { ledgerNanoX, ledgerNanoSPlus, ledgerStax, - ledgerFlex; + ledgerFlex, + BitBox02, + BitBox02Nova; } enum HardwareWalletConnectionType { usb, ble, - nfc; + nfc, + qr; } extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType { diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 9f199443d..04dadaaed 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -181,22 +181,22 @@ class CWEthereum extends Ethereum { String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) - .setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath); - } - - @override - Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, - {int index = 0, int limit = 5}) async { - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection); - try { - return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } catch (err) { - printV(err); - throw err; + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) { + if (service is EVMChainLedgerService) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection( + service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath); + } else if (service is EVMChainBitboxService) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials) + .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath); } } + @override + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) => + EVMChainLedgerService(connection); + + @override + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) => + EVMChainBitboxService(manager); @override List getDefaultTokenContractAddresses() { diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index dabda4e5f..0758af151 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -199,27 +199,23 @@ class CWPolygon extends Polygon { ); @override - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection( - connection, - wallet.walletInfo.derivationInfo?.derivationPath, - ); + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) { + if (service is EVMChainLedgerService) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection( + service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath); + } else if (service is EVMChainBitboxService) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials) + .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath); + } } @override - Future> getHardwareWalletAccounts( - LedgerViewModel ledgerVM, { - int index = 0, - int limit = 5, - }) async { - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection); - try { - return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } catch (err) { - printV(err); - throw err; - } - } + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) => + EVMChainLedgerService(connection); + + @override + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) => + EVMChainBitboxService(manager, chainId: 137); @override List getDefaultTokenContractAddresses() => diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart index 16e8421e0..b95bb7e8a 100644 --- a/lib/reactions/on_authentication_state_change.dart +++ b/lib/reactions/on_authentication_state_change.dart @@ -14,6 +14,8 @@ import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/widgets.dart'; import 'package:mobx/mobx.dart'; @@ -60,8 +62,10 @@ void startAuthenticationStateChange( (route) => false, arguments: ConnectDevicePageParams( walletType: WalletType.monero, + hardwareWalletType: HardwareWalletType.ledger, onConnectDevice: (context, ledgerVM) async { - monero!.setGlobalLedgerConnection(ledgerVM.connection); + if (ledgerVM is LedgerViewModel) + monero!.setGlobalLedgerConnection(ledgerVM.connection); showPopUp( context: context, builder: (context) => AlertWithOneAction( diff --git a/lib/router.dart b/lib/router.dart index ad9c1fa9b..08ff50b72 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -23,6 +23,7 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart'; import 'package:cake_wallet/cake_pay/cake_pay.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart'; +import 'package:cake_wallet/src/screens/connect_device/select_device_manufacturer_page.dart'; import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_page.dart'; @@ -39,6 +40,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/dev/network_requests.dart'; +import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart'; import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart'; @@ -130,6 +132,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; @@ -213,7 +216,6 @@ Route createRoute(RouteSettings settings) { arguments: NewWalletArguments(type: type), ), isCreate: true, - isHardwareWallet: false, ), ), ); @@ -245,12 +247,15 @@ Route createRoute(RouteSettings settings) { case Routes.chooseHardwareWalletAccount: final arguments = settings.arguments as List; final type = arguments[0] as WalletType; - final walletVM = getIt.get(param1: type); + final hardwareWallet = arguments [1] as HardwareWalletType; + + final walletVM = getIt.get( + param1: type, param2: getIt(param1: hardwareWallet)); if (type == WalletType.monero) - return CupertinoPageRoute(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM)); + return handleRouteWithPlatformAwareness((_) => MoneroHardwareWalletOptionsPage(walletVM)); - return CupertinoPageRoute(builder: (_) => SelectHardwareWalletAccountPage(walletVM)); + return handleRouteWithPlatformAwareness((_) => SelectHardwareWalletAccountPage(walletVM)); case Routes.setupPin: Function(PinCodeState, String)? callback; @@ -272,7 +277,6 @@ Route createRoute(RouteSettings settings) { Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg); }, isCreate: false, - isHardwareWallet: false, ), ), ); @@ -301,41 +305,59 @@ Route createRoute(RouteSettings settings) { Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg); }, isCreate: false, - isHardwareWallet: false, ), ), ); case Routes.restoreWalletFromHardwareWallet: + final arguments = settings.arguments as Map?; + final showUnavailable = (arguments?['showUnavailable'] as bool?) ?? true; + final onSelect = arguments?['onSelect'] as void Function(BuildContext, HardwareWalletType)?; + final availableHardwareWalletTypes = + arguments?['availableHardwareWalletTypes'] as List?; + + return handleRouteWithPlatformAwareness((_) => SelectDeviceManufacturerPage( + showUnavailable: showUnavailable, + onSelect: onSelect, + availableHardwareWalletTypes: availableHardwareWalletTypes, + )); + + case Routes.connectHardwareWallet: + final arguments = settings.arguments as List; + final hardwareWalletType = (arguments[0] as HardwareWalletType?) ?? HardwareWalletType.ledger; + if (isSingleCoin) { - return MaterialPageRoute( - builder: (_) => ConnectDevicePage( + return handleRouteWithPlatformAwareness( + (_) => ConnectDevicePage( ConnectDevicePageParams( walletType: availableWalletTypes.first, + hardwareWalletType: hardwareWalletType, onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed( Routes.chooseHardwareWalletAccount, - arguments: [availableWalletTypes.first]), + arguments: [availableWalletTypes.first, hardwareWalletType]), isReconnect: false, ), getIt.get(), ), ); } - return CupertinoPageRoute( - builder: (_) => getIt.get( + return handleRouteWithPlatformAwareness( + (_) => getIt.get( param1: NewWalletTypeArguments( onTypeSelected: (BuildContext context, WalletType type) { final arguments = ConnectDevicePageParams( walletType: type, - onConnectDevice: (BuildContext context, _) => Navigator.of(context) - .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]), + hardwareWalletType: hardwareWalletType, + onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [type, hardwareWalletType]), isReconnect: false, ); Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments); }, isCreate: false, - isHardwareWallet: true, + hardwareWalletType: hardwareWalletType, ), ), ); @@ -347,7 +369,6 @@ Route createRoute(RouteSettings settings) { onTypeSelected: (BuildContext context, WalletType type) => Navigator.of(context).pop(type), isCreate: false, - isHardwareWallet: false, ), ), ); @@ -360,9 +381,8 @@ Route createRoute(RouteSettings settings) { case Routes.restoreWallet: final args = settings.arguments as Map?; final walletType = args?['walletType'] as WalletType; - final restoredWallet = args?['restoredWallet'] as RestoredWallet?; return MaterialPageRoute( - builder: (_) => getIt.get(param1: walletType, param2: restoredWallet)); + builder: (_) => getIt.get(param1: walletType, param2: args)); case Routes.restoreWalletChooseDerivation: return MaterialPageRoute( @@ -873,8 +893,10 @@ Route createRoute(RouteSettings settings) { case Routes.connectDevices: final params = settings.arguments as ConnectDevicePageParams; + return MaterialPageRoute( - builder: (_) => ConnectDevicePage(params, getIt.get())); + builder: (_) => ConnectDevicePage( + params, getIt.get(param1: params.hardwareWalletType))); case Routes.walletGroupDescription: final walletType = settings.arguments as WalletType; @@ -920,6 +942,11 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get(), ); + case Routes.devQRTools: + return MaterialPageRoute( + builder: (_) => getIt.get(), + ); + case Routes.devNetworkRequests: return MaterialPageRoute( builder: (_) => getIt.get(), diff --git a/lib/routes.dart b/lib/routes.dart index 489495664..3e18c36f6 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -11,6 +11,7 @@ class Routes { static const restoreWalletFromHardwareWallet = '/restore/hardware_wallet'; static const restoreWalletTypeFromQR = '/restore_wallet_from_qr_code'; static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation'; + static const connectHardwareWallet = '/restore/hardware_wallet/devices'; static const chooseHardwareWalletAccount = '/restore/hardware_wallet/accounts'; static const dashboard = '/dashboard'; static const send = '/send'; @@ -123,6 +124,7 @@ class Routes { static const devBackgroundSyncLogs = '/dev/background_sync_logs'; static const devSocketHealthLogs = '/dev/socket_health_logs'; static const devNetworkRequests = '/dev/network_requests'; + static const devQRTools = '/dev/qr_tools'; static const devExchangeProviderLogs = '/dev/exchange_provider_logs'; static const signPage = '/sign_page'; diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index ffa9e5470..204e0eb64 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -9,23 +10,25 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_wid import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/themes/core/material_base_theme.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.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'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; -typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel); +typedef OnConnectDevice = void Function(BuildContext, HardwareWalletViewModel); class ConnectDevicePageParams { final WalletType walletType; final OnConnectDevice onConnectDevice; final bool allowChangeWallet; final bool isReconnect; + final HardwareWalletType hardwareWalletType; ConnectDevicePageParams({ required this.walletType, + required this.hardwareWalletType, required this.onConnectDevice, this.allowChangeWallet = false, this.isReconnect = true, @@ -37,9 +40,9 @@ class ConnectDevicePage extends BasePage { final OnConnectDevice onConnectDevice; final bool allowChangeWallet; final bool isReconnect; - final LedgerViewModel ledgerVM; + final HardwareWalletViewModel hardwareWalletVM; - ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM) + ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM) : walletType = params.walletType, onConnectDevice = params.onConnectDevice, allowChangeWallet = params.allowChangeWallet, @@ -51,8 +54,7 @@ class ConnectDevicePage extends BasePage { : S.current.restore_title_from_hardware_wallet; @override - Widget? leading(BuildContext context) => - !isReconnect ? super.leading(context) : null; + Widget? leading(BuildContext context) => !isReconnect ? super.leading(context) : null; @override Widget body(BuildContext context) => PopScope( @@ -61,7 +63,7 @@ class ConnectDevicePage extends BasePage { walletType, onConnectDevice, allowChangeWallet, - ledgerVM, + hardwareWalletVM, currentTheme, )); } @@ -70,14 +72,14 @@ class ConnectDevicePageBody extends StatefulWidget { final WalletType walletType; final OnConnectDevice onConnectDevice; final bool allowChangeWallet; - final LedgerViewModel ledgerVM; + final HardwareWalletViewModel hardwareWalletVM; final MaterialThemeBase currentTheme; const ConnectDevicePageBody( this.walletType, this.onConnectDevice, this.allowChangeWallet, - this.ledgerVM, + this.hardwareWalletVM, this.currentTheme, ); @@ -86,13 +88,13 @@ class ConnectDevicePageBody extends StatefulWidget { } class ConnectDevicePageBodyState extends State { - var bleDevices = []; - var usbDevices = []; + var bleDevices = []; + var usbDevices = []; late Timer? _usbRefreshTimer = null; late Timer? _bleRefreshTimer = null; late Timer? _bleStateTimer = null; - late StreamSubscription? _bleRefresh = null; + late StreamSubscription? _bleRefresh = null; bool longWait = false; Timer? _longWaitTimer; @@ -101,21 +103,21 @@ class ConnectDevicePageBodyState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - _bleStateTimer = Timer.periodic( - Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState()); + _bleStateTimer = + Timer.periodic(Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState()); - _bleRefreshTimer = - Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); + _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); if (Platform.isAndroid) { - _usbRefreshTimer = - Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); + _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); } - _longWaitTimer = Timer(Duration(seconds: 10), () { - if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty) - setState(() => longWait = true); - }); + if (widget.hardwareWalletVM.hasBluetooth) { + _longWaitTimer = Timer(Duration(seconds: 10), () { + if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty) + setState(() => longWait = true); + }); + } }); } @@ -127,35 +129,25 @@ class ConnectDevicePageBodyState extends State { _bleRefresh?.cancel(); _longWaitTimer?.cancel(); - widget.ledgerVM.stopScanning(); + widget.hardwareWalletVM.stopScanning(); super.dispose(); } Future _refreshUsbDevices() async { - final dev = await widget.ledgerVM.ledgerPlusUSB.devices; + final dev = await widget.hardwareWalletVM.getAllUsbDevices(); if (usbDevices.length != dev.length) setState(() => usbDevices = dev); - // _usbRefresh = widget.ledgerVM - // .scanForUsbDevices() - // .listen((device) => setState(() => usbDevices.add(device))) - // ..onError((e) { - // throw e.toString(); - // }); - // Keep polling until the lfp lib gets updated - // _usbRefreshTimer?.cancel(); - // _usbRefreshTimer = null; } Future _refreshBleDevices() async { try { - if (widget.ledgerVM.bleIsEnabled) { - _bleRefresh = - widget.ledgerVM.scanForBleDevices().listen((device) => setState(() { - bleDevices.add(device); - if (longWait) longWait = false; - })) - ..onError((e) { - throw e.toString(); - }); + if (widget.hardwareWalletVM.isBleEnabled) { + _bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen((device) => setState(() { + bleDevices.add(device); + if (longWait) longWait = false; + })) + ..onError((e) { + throw e.toString(); + }); _bleRefreshTimer?.cancel(); _bleRefreshTimer = null; } @@ -164,25 +156,35 @@ class ConnectDevicePageBodyState extends State { } } - Future _connectToDevice(LedgerDevice device) async { - final isConnected = - await widget.ledgerVM.connectLedger(device, widget.walletType); - if (isConnected) widget.onConnectDevice(context, widget.ledgerVM); + Future _connectToDevice(HardwareWalletDevice device) async { + final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType); + if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM); } - String _getDeviceTileLeading(LedgerDeviceType deviceInfo) { - switch (deviceInfo) { - case LedgerDeviceType.nanoX: + String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) { + switch (deviceType) { + case HardwareWalletDeviceType.ledgerNanoX: return 'assets/images/hardware_wallet/ledger_nano_x.png'; - case LedgerDeviceType.stax: + case HardwareWalletDeviceType.ledgerStax: return 'assets/images/hardware_wallet/ledger_stax.png'; - case LedgerDeviceType.flex: + case HardwareWalletDeviceType.ledgerFlex: return 'assets/images/hardware_wallet/ledger_flex.png'; + case HardwareWalletDeviceType.BitBox02: + return 'assets/images/hardware_wallet/device_bitbox.svg'; + default: return 'assets/images/hardware_wallet/ledger_nano_x.png'; } } + String get description { + if (!Platform.isAndroid) return S.of(context).connect_your_hardware_wallet_ble; + + if (widget.hardwareWalletVM.hasBluetooth) return S.of(context).connect_your_hardware_wallet; + + return S.of(context).connect_your_hardware_wallet_usb; + } + @override Widget build(BuildContext context) { return Container( @@ -197,11 +199,8 @@ class ConnectDevicePageBodyState extends State { Padding( padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), child: Text( - Platform.isAndroid - ? S.of(context).connect_your_hardware_wallet - : S.of(context).connect_your_hardware_wallet_ios, - style: Theme.of(context) - .textTheme.titleMedium, + description, + style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), ), @@ -211,22 +210,20 @@ class ConnectDevicePageBodyState extends State { padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), child: Text( S.of(context).if_you_dont_see_your_device, - style: Theme.of(context) - .textTheme.titleMedium, + style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), ), ), Observer( builder: (_) => Offstage( - offstage: widget.ledgerVM.bleIsEnabled, + offstage: widget.hardwareWalletVM.isBleEnabled || + !widget.hardwareWalletVM.hasBluetooth, child: Padding( - padding: - EdgeInsets.only(left: 20, right: 20, bottom: 20), + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), child: Text( S.of(context).ledger_please_enable_bluetooth, - style: Theme.of(context) - .textTheme.titleMedium, + style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), ), @@ -239,8 +236,7 @@ class ConnectDevicePageBodyState extends State { width: double.infinity, child: Text( S.of(context).bluetooth, - style: Theme.of(context) - .textTheme.bodyMedium, + style: Theme.of(context).textTheme.bodyMedium, ), ), ), @@ -251,7 +247,7 @@ class ConnectDevicePageBodyState extends State { child: DeviceTile( onPressed: () => _connectToDevice(device), title: device.name, - leading: _getDeviceTileLeading(device.deviceInfo), + leading: _getDeviceTileLeading(device.type), connectionType: device.connectionType, ), ), @@ -265,8 +261,7 @@ class ConnectDevicePageBodyState extends State { width: double.infinity, child: Text( S.of(context).usb, - style: Theme.of(context) - .textTheme.bodyMedium, + style: Theme.of(context).textTheme.bodyMedium, ), ), ), @@ -277,7 +272,7 @@ class ConnectDevicePageBodyState extends State { child: DeviceTile( onPressed: () => _connectToDevice(device), title: device.name, - leading: _getDeviceTileLeading(device.deviceInfo), + leading: _getDeviceTileLeading(device.type), connectionType: device.connectionType, ), ), @@ -287,10 +282,8 @@ class ConnectDevicePageBodyState extends State { if (widget.allowChangeWallet) ...[ PrimaryButton( text: S.of(context).wallets, - color: Theme.of(context) - .colorScheme.primary, - textColor: Theme.of(context) - .colorScheme.onPrimary, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, onPressed: _onChangeWallet, ) ], @@ -312,8 +305,8 @@ class ConnectDevicePageBodyState extends State { void _onChangeWallet() { Navigator.of(context).pushNamed( Routes.walletList, - arguments: (BuildContext context) => Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false), + arguments: (BuildContext context) => + Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false), ); } diff --git a/lib/src/screens/connect_device/select_device_manufacturer_page.dart b/lib/src/screens/connect_device/select_device_manufacturer_page.dart new file mode 100644 index 000000000..b1192ac43 --- /dev/null +++ b/lib/src/screens/connect_device/select_device_manufacturer_page.dart @@ -0,0 +1,183 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/connect_device/widgets/dotted_divider.dart'; +import 'package:cake_wallet/src/screens/connect_device/widgets/manufacturer_option_tile.dart'; +import 'package:cake_wallet/utils/permission_handler.dart'; +import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class SelectDeviceManufacturerPage extends BasePage { + SelectDeviceManufacturerPage({ + this.showUnavailable = true, + this.onSelect, + this.availableHardwareWalletTypes, + }); + + final bool showUnavailable; + final void Function(BuildContext, HardwareWalletType)? onSelect; + final List? availableHardwareWalletTypes; + + @override + String get title => S.current.select_manufacturer_title; + + @override + AppBarStyle get appBarStyle => AppBarStyle.regular; + + List<_DeviceManufacturer> get availableManufacturers => [ + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25), + hardwareWalletType: HardwareWalletType.ledger, + ), + // _DeviceManufacturer( + // image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25), + // hardwareWalletType: HardwareWalletType.bitbox, + // tag: S.current.new_tag, + // ), + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25), + hardwareWalletType: HardwareWalletType.cupcake, + tag: S.current.new_tag, + ), + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25), + hardwareWalletType: HardwareWalletType.coldcard, + tag: S.current.new_tag, + ), + // _DeviceManufacturer( + // image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25), + // hardwareWalletType: HardwareWalletType.seedsigner, + // tag: S.current.new_tag, + // ), + ] + .where((e) => availableHardwareWalletTypes == null + ? ![HardwareWalletType.cupcake].contains(e.hardwareWalletType) + : availableHardwareWalletTypes!.contains(e.hardwareWalletType)) + .toList(); + + List<_DeviceManufacturer> get comingManufacturers => [ + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25), + tag: S.current.coming_soon_tag, + ), + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25), + hardwareWalletType: HardwareWalletType.bitbox, + tag: S.current.coming_soon_tag, + ), + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25), + hardwareWalletType: HardwareWalletType.seedsigner, + tag: S.current.coming_soon_tag, + ), + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25), + tag: S.current.coming_soon_tag, + ), + _DeviceManufacturer( + image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25), + tag: S.current.coming_soon_tag, + ), + ]; + + @override + Widget body(BuildContext context) => Container( + child: Center( + child: Container( + padding: EdgeInsets.only(left: 24, right: 24), + child: Column( + children: [ + ...availableManufacturers.map( + (manufacturer) => Padding( + padding: EdgeInsets.only(top: 10), + child: ManufacturerOptionTile( + image: manufacturer.image, + tag: manufacturer.tag, + onPressed: () { + if (onSelect != null) + return onSelect!.call(context, manufacturer.hardwareWalletType!); + + if (isAirgappedWallet(manufacturer.hardwareWalletType)) { + _onScanQRCode(context, manufacturer.hardwareWalletType!); + } else if (manufacturer.hardwareWalletType != null) { + Navigator.pushNamed(context, Routes.connectHardwareWallet, + arguments: [manufacturer.hardwareWalletType]); + } + }, + isDarkTheme: currentTheme.isDark, + ), + ), + ), + if (showUnavailable) ...[ + Padding( + padding: EdgeInsets.only(top: 20, bottom: 10), + child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer), + ), + ...comingManufacturers.map( + (manufacturer) => Padding( + padding: EdgeInsets.only(top: 10), + child: ManufacturerOptionTile( + image: manufacturer.image, + tag: manufacturer.tag, + onPressed: () => + Fluttertoast.showToast(msg: 'One more tap and it might work'), + // Ester egg + isDarkTheme: currentTheme.isDark, + isUnavailable: true, + ), + ), + ), + ] + ], + ), + ), + ), + ); + + bool isAirgappedWallet(HardwareWalletType? type) => [ + HardwareWalletType.cupcake, + HardwareWalletType.coldcard, + HardwareWalletType.seedsigner, + HardwareWalletType.keystone, + ].contains(type); + + bool isRestoring = false; + + Future _onScanQRCode(BuildContext context, HardwareWalletType type) async { + final isCameraPermissionGranted = + await PermissionHandler.checkPermission(Permission.camera, context); + + if (!isCameraPermissionGranted) return; + try { + if (isRestoring) return; + + isRestoring = true; + + final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context); + + final params = { + 'walletType': restoredWallet.type, + 'restoredWallet': restoredWallet, + 'hardwareWalletType': type, + }; + + Navigator.pushNamed(context, Routes.restoreWallet, arguments: params) + .then((_) => isRestoring = false); + } catch (e) { + printV(e.toString()); + } + } +} + +class _DeviceManufacturer { + final SvgPicture image; + final HardwareWalletType? hardwareWalletType; + final String? tag; + + _DeviceManufacturer({required this.image, this.hardwareWalletType, this.tag}); +} diff --git a/lib/src/screens/connect_device/widgets/device_tile.dart b/lib/src/screens/connect_device/widgets/device_tile.dart index 7808570d2..803a648bb 100644 --- a/lib/src/screens/connect_device/widgets/device_tile.dart +++ b/lib/src/screens/connect_device/widgets/device_tile.dart @@ -1,5 +1,6 @@ +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; import 'package:flutter/material.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class DeviceTile extends StatelessWidget { const DeviceTile({ @@ -12,15 +13,15 @@ class DeviceTile extends StatelessWidget { final VoidCallback onPressed; final String title; final String? leading; - final ConnectionType? connectionType; + final HardwareWalletConnectionType? connectionType; String? get connectionTypeIcon { switch (connectionType) { - case ConnectionType.ble: + case HardwareWalletConnectionType.ble: return 'assets/images/bluetooth.png'; - case ConnectionType.usb: + case HardwareWalletConnectionType.usb: return 'assets/images/usb.png'; - case null: + default: return null; } } @@ -42,12 +43,14 @@ class DeviceTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (leading != null) + if (leading != null && !leading!.endsWith(".svg")) Image.asset( leading!, height: 30, color: Theme.of(context).colorScheme.onSurface, ), + if (leading != null && leading!.endsWith(".svg")) + SvgPicture.asset(leading!, height: 30), Expanded( child: Padding( padding: EdgeInsets.only(left: 16), diff --git a/lib/src/screens/connect_device/widgets/dotted_divider.dart b/lib/src/screens/connect_device/widgets/dotted_divider.dart new file mode 100644 index 000000000..aa783cb93 --- /dev/null +++ b/lib/src/screens/connect_device/widgets/dotted_divider.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class DottedDivider extends StatelessWidget { + final Color color; + + const DottedDivider({super.key, required this.color}); + + @override + Widget build(BuildContext context) => SizedBox( + width: double.infinity, + child: CustomPaint( + painter: _DashedLinePainter(color: color), + ), + ); +} + +class _DashedLinePainter extends CustomPainter { + final Color color; + + const _DashedLinePainter({required this.color}); + + @override + void paint(Canvas canvas, Size size) { + double dashWidth = 9, dashSpace = 5, startX = 0; + final paint = Paint() + ..color = color + ..strokeWidth = 1; + while (startX < size.width) { + canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint); + startX += dashWidth + dashSpace; + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} diff --git a/lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart b/lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart new file mode 100644 index 000000000..6e49ae9d4 --- /dev/null +++ b/lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart @@ -0,0 +1,80 @@ +import 'package:cake_wallet/themes/utils/custom_theme_colors.dart'; +import 'package:flutter/material.dart'; + +class ManufacturerOptionTile extends StatelessWidget { + const ManufacturerOptionTile({ + required this.onPressed, + required this.image, + required this.isDarkTheme, + this.tag, + this.isUnavailable = false, + }); + + final VoidCallback onPressed; + final Widget image; + final String? tag; + final bool isUnavailable; + final bool isDarkTheme; + + @override + Widget build(BuildContext context) => GestureDetector( + onTap: onPressed, + child: Opacity( + opacity: isUnavailable ? 0.5 : 1, + child: Container( + width: double.infinity, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: LinearGradient( + colors: [ + isDarkTheme + ? CustomThemeColors.cardGradientColorPrimaryDark + : CustomThemeColors.cardGradientColorPrimaryLight, + isDarkTheme + ? CustomThemeColors.cardGradientColorSecondaryDark + : CustomThemeColors.cardGradientColorSecondaryLight, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + padding: EdgeInsets.all(24), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container(height: 25, child: image), + if (tag != null) + Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.transparent), + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.surfaceContainerLow), + padding: EdgeInsets.symmetric(horizontal: 5), + margin: EdgeInsets.only(left: 5), + child: Text(tag!)) + ], + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + size: 16, + color: Theme.of(context).colorScheme.onSurface, + ) + ], + ), + ), + ), + ); +} diff --git a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart index ee377651b..fc526aae9 100644 --- a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart +++ b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/src/widgets/standard_switch.dart'; import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -27,6 +28,23 @@ class CryptoBalanceWidget extends StatelessWidget { final btcLockLight = 'assets/images/btc_lock_light.png'; final btcLockDark = 'assets/images/btc_lock_dark.png'; + String? get hardwareWalletIcon { + switch (dashboardViewModel.wallet.hardwareWalletType) { + case null: + return null; + case HardwareWalletType.ledger: + return "assets/images/hardware_wallet/device_ledger_nano_x.svg"; + case HardwareWalletType.bitbox: + return "assets/images/hardware_wallet/device_bitbox.svg"; + case HardwareWalletType.cupcake: + return "assets/images/cupcake.svg"; + case HardwareWalletType.coldcard: + case HardwareWalletType.seedsigner: + case HardwareWalletType.keystone: + return "assets/images/hardware_wallet/device_qr.svg"; + } + } + @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -86,10 +104,10 @@ class CryptoBalanceWidget extends StatelessWidget { maxLines: 1, textAlign: TextAlign.center, ), - if (dashboardViewModel.wallet.isHardwareWallet) + if (hardwareWalletIcon != null) Container( - child: Image.asset( - 'assets/images/hardware_wallet/ledger_nano_x.png', + child: SvgPicture.asset( + hardwareWalletIcon!, width: 24, color: Theme.of(context).colorScheme.onSurface, ), diff --git a/lib/src/screens/dev/qr_tools_page.dart b/lib/src/screens/dev/qr_tools_page.dart new file mode 100644 index 000000000..67ba72478 --- /dev/null +++ b/lib/src/screens/dev/qr_tools_page.dart @@ -0,0 +1,39 @@ +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart'; +import 'package:cake_wallet/view_model/dev/qr_tools_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class DevQRToolsPage extends BasePage { + @override + String? get title => "[dev] *QR tools"; + + final QRToolsViewModel viewModel = QRToolsViewModel(); + + late final textCtrl = TextEditingController(text: viewModel.input); + + @override + Widget body(BuildContext context) { + return Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: textCtrl, + maxLines: 8, + onChanged: (value) { + viewModel.input = value; + } + ), + Observer( + builder: (_) { + return Expanded( + child: JsonExplorer(data: viewModel.data, title: "result"), + ); + }, + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index dbdcd910c..2e58032b3 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -1,7 +1,7 @@ -import 'package:cake_wallet/core/new_wallet_arguments.dart'; -import 'package:cake_wallet/core/new_wallet_type_arguments.dart'; import 'dart:io'; +import 'package:cake_wallet/core/new_wallet_arguments.dart'; +import 'package:cake_wallet/core/new_wallet_type_arguments.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_utils.dart'; import 'package:cake_wallet/routes.dart'; @@ -11,12 +11,12 @@ import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_aler import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/search_bar_widget.dart'; -import 'package:cake_wallet/themes/core/material_base_theme.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/new_wallet_type_view_model.dart'; import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/hardware/device_connection_type.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; @@ -51,7 +51,7 @@ class NewWalletTypePage extends BasePage { isCreate: newWalletTypeArguments.isCreate, newWalletTypeViewModel: newWalletTypeViewModel, onTypeSelected: newWalletTypeArguments.onTypeSelected, - isHardwareWallet: newWalletTypeArguments.isHardwareWallet, + hardwareWalletType: newWalletTypeArguments.hardwareWalletType, ); } @@ -61,14 +61,16 @@ class WalletTypeForm extends StatefulWidget { required this.isCreate, required this.newWalletTypeViewModel, this.onTypeSelected, - required this.isHardwareWallet, + this.hardwareWalletType, }); final bool isCreate; final Image walletImage; final NewWalletTypeViewModel newWalletTypeViewModel; final void Function(BuildContext, WalletType)? onTypeSelected; - final bool isHardwareWallet; + final HardwareWalletType? hardwareWalletType; + + bool get isHardwareWallet => hardwareWalletType != null; @override WalletTypeFormState createState() => WalletTypeFormState(); @@ -90,7 +92,9 @@ class WalletTypeFormState extends State { types = filteredTypes = availableWalletTypes .where((element) => !widget.isHardwareWallet || - DeviceConnectionType.supportedConnectionTypes(element, Platform.isIOS).isNotEmpty) + DeviceConnectionType.supportedConnectionTypes( + element, widget.hardwareWalletType!, Platform.isIOS) + .isNotEmpty) .toList(); super.initState(); @@ -148,7 +152,8 @@ class WalletTypeFormState extends State { isSelected: selected == type, onTap: () => setState(() => selected = type), deviceConnectionTypes: widget.isHardwareWallet - ? DeviceConnectionType.supportedConnectionTypes(type, Platform.isIOS) + ? DeviceConnectionType.supportedConnectionTypes( + type, widget.hardwareWalletType!, Platform.isIOS) : [], ), ), diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index fcb34df18..b44f30a59 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -111,7 +111,7 @@ class QRWidget extends StatelessWidget { ), if (addressListViewModel.isPayjoinUnavailable && !addressListViewModel.isSilentPayments && - !addressListViewModel.isCupcake) ...[ + !addressListViewModel.isBitcoinViewOnly) ...[ GestureDetector( onTap: () => _onPayjoinInactivePressed(context), child: Row( diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index ea1bff6bb..63a9c60f4 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -1,10 +1,9 @@ -import 'dart:io'; - import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/option_tile.dart'; +import 'package:cake_wallet/themes/core/material_base_theme.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/permission_handler.dart'; @@ -12,11 +11,9 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; -import 'package:cw_core/hardware/device_connection_type.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:cake_wallet/themes/core/material_base_theme.dart'; class RestoreOptionsPage extends BasePage { RestoreOptionsPage({required this.isNewInstall}); @@ -27,9 +24,8 @@ class RestoreOptionsPage extends BasePage { final bool isNewInstall; @override - Widget body(BuildContext context) { - return _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type); - } + Widget body(BuildContext context) => + _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type); } class _RestoreOptionsBody extends StatefulWidget { @@ -45,41 +41,25 @@ class _RestoreOptionsBody extends StatefulWidget { class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { bool isRestoring = false; - bool get _doesSupportHardwareWallets { - if (isMoneroOnly) { - return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS) - .isNotEmpty; - } + String get imageRestoreHWPath => widget.themeType == ThemeType.dark + ? 'assets/images/restore_hw_dark.png' + : 'assets/images/restore_hw.png'; - return true; - } + String get imageRestoreCupcakePath => widget.themeType == ThemeType.dark + ? 'assets/images/restore_cupcake_dark.png' + : 'assets/images/restore_cupcake.png'; - String get imageRestoreHWPath => - widget.themeType == ThemeType.dark - ? 'assets/images/restore_hw_dark.png' - : 'assets/images/restore_hw.png'; + String get imageRestoreQRPath => widget.themeType == ThemeType.dark + ? 'assets/images/restore_qr_dark.png' + : 'assets/images/restore_qr.png'; - String get imageRestoreCupcakePath => - widget.themeType == ThemeType.dark - ? 'assets/images/restore_cupcake_dark.png' - : 'assets/images/restore_cupcake.png'; + String get imageRestoreHotWalletPath => widget.themeType == ThemeType.dark + ? 'assets/images/restore_hot_wallet_dark.png' + : 'assets/images/restore_hot_wallet.png'; - String get imageRestoreQRPath => - widget.themeType == ThemeType.dark - ? 'assets/images/restore_qr_dark.png' - : 'assets/images/restore_qr.png'; - - - String get imageRestoreHotWalletPath => - widget.themeType == ThemeType.dark - ? 'assets/images/restore_hot_wallet_dark.png' - : 'assets/images/restore_hot_wallet.png'; - - - String get imageRestoreBackupPath => - widget.themeType == ThemeType.dark - ? 'assets/images/restore_backup_dark.png' - : 'assets/images/restore_backup.png'; + String get imageRestoreBackupPath => widget.themeType == ThemeType.dark + ? 'assets/images/restore_backup_dark.png' + : 'assets/images/restore_backup.png'; @override Widget build(BuildContext context) { @@ -116,18 +96,20 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { tag: S.of(context).new_tag, ), ), - if (_doesSupportHardwareWallets) - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - key: ValueKey('restore_options_from_hardware_wallet_button_key'), - onPressed: () => - Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet), - image: imageRestoreHW, - title: S.of(context).restore_title_from_hardware_wallet, - description: S.of(context).restore_description_from_hardware_wallet, - ), + Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + key: ValueKey('restore_options_from_hardware_wallet_button_key'), + onPressed: () => Navigator.pushNamed( + context, + isSingleCoin + ? Routes.connectHardwareWallet + : Routes.restoreWalletFromHardwareWallet), + image: imageRestoreHW, + title: S.of(context).restore_title_from_hardware_wallet, + description: S.of(context).restore_description_from_hardware_wallet, ), + ), if (widget.isNewInstall) Padding( padding: EdgeInsets.only(top: 24), @@ -143,11 +125,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { Padding( padding: EdgeInsets.only(top: 24), child: OptionTile( - key: ValueKey('restore_options_from_qr_button_key'), - onPressed: () => _onScanQRCode(context), - image: imageRestoreQR, - title: S.of(context).scan_qr_code, - description: S.of(context).cold_or_recover_wallet, + key: ValueKey('restore_options_from_qr_button_key'), + onPressed: () => _onScanQRCode(context), + image: imageRestoreQR, + title: S.of(context).scan_qr_code, + description: S.of(context).cold_or_recover_wallet, ), ), ], @@ -184,7 +166,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context); - final params = {'walletType': restoredWallet.type, 'restoredWallet': restoredWallet}; + final params = { + 'walletType': restoredWallet.type, + 'restoredWallet': restoredWallet, + 'hardwareWalletType': HardwareWalletType.cupcake, + }; Navigator.pushNamed(context, Routes.restoreWallet, arguments: params).then((_) { if (mounted) setState(() => isRestoring = false); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index ef4d62688..291541566 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -440,18 +440,21 @@ class SendPage extends BasePage { } if (sendViewModel.wallet.isHardwareWallet) { - if (!sendViewModel.ledgerViewModel!.isConnected) { + if (!sendViewModel.hardwareWalletViewModel!.isConnected) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: sendViewModel.walletType, + hardwareWalletType: + sendViewModel.wallet.walletInfo.hardwareWalletType!, onConnectDevice: (BuildContext context, _) { - sendViewModel.ledgerViewModel! - .setLedger(sendViewModel.wallet); + sendViewModel.hardwareWalletViewModel! + .initWallet(sendViewModel.wallet); Navigator.of(context).pop(); }, )); } else { - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet); + sendViewModel.hardwareWalletViewModel! + .initWallet(sendViewModel.wallet); } } diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 9c943d0e0..0cb32270b 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart'; import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.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'; @@ -26,16 +27,14 @@ class OtherSettingsPage extends BasePage { final OtherSettingsViewModel _otherSettingsViewModel; @override - Widget body(BuildContext context) { - return Observer( - builder: (_) { - return Container( + Widget body(BuildContext context) => Observer( + builder: (_) => Container( padding: EdgeInsets.only(top: 10), child: Column( children: [ if (_otherSettingsViewModel.displayTransactionPriority) - _otherSettingsViewModel.walletType == WalletType.bitcoin ? - SettingsPriorityPickerCell( + _otherSettingsViewModel.walletType == WalletType.bitcoin + ? SettingsPriorityPickerCell( title: S.current.settings_fee_priority, items: priorityForWalletType(_otherSettingsViewModel.walletType), displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority, @@ -44,8 +43,8 @@ class OtherSettingsPage extends BasePage { onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected, customValue: _otherSettingsViewModel.customBitcoinFeeRate, maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(), - ) : - SettingsPickerCell( + ) + : SettingsPickerCell( title: S.current.settings_fee_priority, items: priorityForWalletType(_otherSettingsViewModel.walletType), displayItem: _otherSettingsViewModel.getDisplayPriority, @@ -55,75 +54,96 @@ class OtherSettingsPage extends BasePage { if (_otherSettingsViewModel.changeRepresentativeEnabled) SettingsCellWithArrow( title: S.current.change_rep, - handler: (BuildContext context) => - Navigator.of(context).pushNamed(Routes.changeRep), + handler: (context) => Navigator.of(context).pushNamed(Routes.changeRep), + ), + if (_otherSettingsViewModel.changeHardwareWalletTypeEnabled) + SettingsCellWithArrow( + title: "Hardware wallet manufacturer", + handler: (context) => Navigator.of(context) + .pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: { + "showUnavailable": false, + "availableHardwareWalletTypes": [ + HardwareWalletType.cupcake, + HardwareWalletType.coldcard, + HardwareWalletType.seedsigner, + ], + "onSelect": (BuildContext context, HardwareWalletType hwType) async { + await _otherSettingsViewModel.onHardwareWalletTypeChanged(hwType); + Navigator.pop(context); + }, + }), ), SettingsCellWithArrow( title: S.current.settings_terms_and_conditions, - handler: (BuildContext context) => - Navigator.of(context).pushNamed(Routes.readDisclaimer), + handler: (context) => Navigator.of(context).pushNamed(Routes.readDisclaimer), ), - if (FeatureFlag.hasDevOptions && _otherSettingsViewModel.walletType == WalletType.monero) + if (FeatureFlag.hasDevOptions && + _otherSettingsViewModel.walletType == WalletType.monero) SettingsCellWithArrow( title: '[dev] monero background sync', - handler: (BuildContext context) => + handler: (context) => Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync), ), - if (FeatureFlag.hasDevOptions && [WalletType.monero, WalletType.wownero, WalletType.zano].contains(_otherSettingsViewModel.walletType)) + if (FeatureFlag.hasDevOptions && + [WalletType.monero, WalletType.wownero, WalletType.zano] + .contains(_otherSettingsViewModel.walletType)) SettingsCellWithArrow( title: '[dev] xmr call profiler', - handler: (BuildContext context) => + handler: (context) => Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler), ), - if (FeatureFlag.hasDevOptions && [WalletType.monero].contains(_otherSettingsViewModel.walletType)) + if (FeatureFlag.hasDevOptions && + [WalletType.monero].contains(_otherSettingsViewModel.walletType)) SettingsCellWithArrow( title: '[dev] xmr wallet cache debug', - handler: (BuildContext context) => + handler: (context) => Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug), ), if (FeatureFlag.hasDevOptions) SettingsCellWithArrow( title: '[dev] shared preferences', - handler: (BuildContext context) => + handler: (context) => Navigator.of(context).pushNamed(Routes.devSharedPreferences), ), if (FeatureFlag.hasDevOptions) SettingsCellWithArrow( title: '[dev] secure storage preferences', - handler: (BuildContext context) => + handler: (context) => Navigator.of(context).pushNamed(Routes.devSecurePreferences), ), if (FeatureFlag.hasDevOptions) SettingsCellWithArrow( title: '[dev] background sync logs', - handler: (BuildContext context) => + handler: (context) => Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs), ), if (FeatureFlag.hasDevOptions) SettingsCellWithArrow( title: '[dev] socket health logs', - handler: (BuildContext context) => - Navigator.of(context).pushNamed(Routes.devSocketHealthLogs), + handler: (context) => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs), ), if (FeatureFlag.hasDevOptions) SettingsCellWithArrow( title: '[dev] network requests logs', - handler: (BuildContext context) => - Navigator.of(context).pushNamed(Routes.devNetworkRequests), + handler: (context) => Navigator.of(context).pushNamed(Routes.devNetworkRequests), ), if (FeatureFlag.hasDevOptions) SettingsCellWithArrow( title: '[dev] exchange provider logs', - handler: (BuildContext context) => + handler: (context) => Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs), ), + if (FeatureFlag.hasDevOptions) + SettingsCellWithArrow( + title: '[dev] *QR tools', + handler: (context) => Navigator.of(context).pushNamed(Routes.devQRTools), + ), Spacer(), SettingsVersionCell( - title: S.of(context).version(_otherSettingsViewModel.currentVersion)), + title: S.of(context).version(_otherSettingsViewModel.currentVersion), + ), ], ), - ); - }, - ); - } + ), + ); } diff --git a/lib/src/screens/ur/animated_ur_page.dart b/lib/src/screens/ur/animated_ur_page.dart index 0ee5e021d..ea2383402 100644 --- a/lib/src/screens/ur/animated_ur_page.dart +++ b/lib/src/screens/ur/animated_ur_page.dart @@ -61,6 +61,7 @@ class AnimatedURPage extends BasePage { child: URQR( urqr: urQr, walletType: animatedURmodel.wallet.type, + hardwareWalletType: animatedURmodel.wallet.hardwareWalletType, ), ), if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header] @@ -125,4 +126,4 @@ class AnimatedURPage extends BasePage { }); } } -} \ No newline at end of file +} diff --git a/lib/src/screens/ur/widgets/urqr.dart b/lib/src/screens/ur/widgets/urqr.dart index b26815239..7649933df 100644 --- a/lib/src/screens/ur/widgets/urqr.dart +++ b/lib/src/screens/ur/widgets/urqr.dart @@ -1,4 +1,3 @@ - import 'dart:async'; import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; @@ -7,67 +6,65 @@ import 'package:cake_wallet/src/screens/ur/widgets/qr_selection_dialog.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; class URQR extends StatefulWidget { - URQR({super.key, required this.urqr, required this.walletType}); + const URQR({super.key, required this.urqr, required this.walletType, this.hardwareWalletType}); final Map urqr; final WalletType walletType; + final HardwareWalletType? hardwareWalletType; + @override - // ignore: library_private_types_in_public_api _URQRState createState() => _URQRState(); } const urFrameTime = 1000 ~/ 5; class _URQRState extends State { - Timer? t; + Timer? _timer; int frame = 0; + int selectedInt = 0; + @override void initState() { - super.initState(); - setState(() { - t = Timer.periodic(const Duration(milliseconds: urFrameTime), (timer) { - _nextFrame(); - }); - }); - } + final keys = widget.urqr.keys.toList(); + if (widget.hardwareWalletType == HardwareWalletType.coldcard) { + selectedInt = 1; + selected = keys[(selectedInt) % keys.length]; + } else if (widget.hardwareWalletType == HardwareWalletType.seedsigner) { + selectedInt = 2; + selected = keys[(selectedInt) % keys.length]; + } - void _nextFrame() { - setState(() { - frame++; - }); + _timer = Timer.periodic(const Duration(milliseconds: urFrameTime), (_) => _nextFrame()); + super.initState(); } @override void dispose() { - t?.cancel(); + _timer?.cancel(); super.dispose(); } - late String selected = - (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first; - int selectedInt = 0; - - List get frames { - return widget.urqr[selected]?.split("\n") ?? []; - } - - late String nextLabel = - widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length]; - - void next() { + String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length]; + void next() => setState(() { final keys = widget.urqr.keys.toList(); - setState(() { - selectedInt++; - nextLabel = keys[(selectedInt + 1) % keys.length]; - selected = keys[(selectedInt) % keys.length]; - }); - } + + selectedInt++; + selected = keys[(selectedInt) % keys.length]; + }); + + late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first; + + + List get frames => widget.urqr[selected]?.split("\n") ?? []; + void _nextFrame() => setState(() => frame++); + @override Widget build(BuildContext context) { @@ -107,45 +104,44 @@ All frames: ); } - Widget _legacySwitch(BuildContext context) { - return SizedBox( - width: double.maxFinite, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: SizedBox( - width: double.maxFinite, - child: PrimaryButton( - onPressed: next, - text: nextLabel, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, + Widget _legacySwitch(BuildContext context) => SizedBox( + width: double.maxFinite, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + width: double.maxFinite, + child: PrimaryButton( + onPressed: next, + text: nextLabel, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ), ), ), - ), - ); - } + ); Widget _newSwitch(BuildContext context) { return Column( children: [ - SizedBox(height: 32), + const SizedBox(height: 32), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.qr_code, - color: Theme.of(context).colorScheme.primary, size: 24), - SizedBox(width: 4), - Text("QR Code Format", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - )), + Icon(Icons.qr_code, color: Theme.of(context).colorScheme.primary, size: 24), + const SizedBox(width: 4), + Text( + "QR Code Format", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), ], ), - SizedBox(height: 16), + const SizedBox(height: 16), Container( - margin: EdgeInsets.symmetric(horizontal: 16), + margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(12), @@ -176,7 +172,7 @@ All frames: ), ), ), - SizedBox(width: 16), + const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -193,10 +189,7 @@ All frames: _getCurrentFormatSubtitle(), style: TextStyle( fontSize: 14, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.6), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ), ], @@ -204,10 +197,7 @@ All frames: ), Icon( Icons.chevron_right, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.4), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), size: 24, ), ], @@ -216,7 +206,7 @@ All frames: ), ), ), - SizedBox(height: 16), + const SizedBox(height: 16), GestureDetector( onTap: () => _showInfoDialog(context), child: Row( @@ -226,35 +216,32 @@ All frames: "What's this for?", style: TextStyle( fontSize: 14, - color: - Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ), - SizedBox(width: 4), + const SizedBox(width: 4), Icon( Icons.info_outline, size: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ], ), ), - SizedBox(height: 32), + const SizedBox(height: 32), ], ); } String _getCurrentFormatName() { if (widget.urqr.isEmpty) return "Unknown"; - final currentKey = - widget.urqr.keys.elementAt(selectedInt % widget.urqr.length); + final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length); return currentKey.split(' ')[0]; } String _getCurrentFormatSubtitle() { if (widget.urqr.isEmpty) return ""; - final currentKey = - widget.urqr.keys.elementAt(selectedInt % widget.urqr.length); + final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length); return currentKey.substring(currentKey.indexOf(' ') + 1); } @@ -264,30 +251,25 @@ All frames: final keys = widget.urqr.keys.toList(); await showPopUp( - context: context, - builder: (BuildContext context) { - return QRFormatSelectionDialog( - formats: keys, - currentIndex: selectedInt, - onFormatSelected: (index) { - Navigator.pop(context); - setState(() { - selectedInt = index; - selected = keys[index]; - nextLabel = keys[(index + 1) % keys.length]; - }); - }, - ); - }); + context: context, + builder: (context) => QRFormatSelectionDialog( + formats: keys, + currentIndex: selectedInt, + onFormatSelected: (index) { + Navigator.pop(context); + setState(() { + selectedInt = index; + selected = keys[index]; + }); + }, + ), + ); } - Future _showInfoDialog(BuildContext context) async { - await showModalBottomSheet( + Future _showInfoDialog(BuildContext context) => showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return QRFormatInfoBottomSheet(); - }); - } + builder: (_) => QRFormatInfoBottomSheet(), + ); } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index b2f3bc605..d8f20d2df 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -21,9 +21,11 @@ import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/wallet_type_utils.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'; @@ -477,8 +479,9 @@ class WalletListBodyState extends State { Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: WalletType.monero, + hardwareWalletType: HardwareWalletType.ledger, onConnectDevice: (context, ledgerVM) async { - monero!.setGlobalLedgerConnection(ledgerVM.connection); + if (ledgerVM is LedgerViewModel) monero!.setGlobalLedgerConnection(ledgerVM.connection); didConnect = true; Navigator.of(context).pop(); }, diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart index 1ae6851a0..8aab2736d 100644 --- a/lib/view_model/dashboard/receive_option_view_model.dart +++ b/lib/view_model/dashboard/receive_option_view_model.dart @@ -28,7 +28,7 @@ abstract class ReceiveOptionViewModelBase with Store { break; case WalletType.litecoin: _options = [ - ...bitcoin!.getLitecoinReceivePageOptions(), + ...bitcoin!.getLitecoinReceivePageOptions(_wallet), ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet) ]; break; diff --git a/lib/view_model/dev/qr_tools_view_model.dart b/lib/view_model/dev/qr_tools_view_model.dart new file mode 100644 index 000000000..76cd285b6 --- /dev/null +++ b/lib/view_model/dev/qr_tools_view_model.dart @@ -0,0 +1,81 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:ur/cbor_lite.dart'; +import 'package:ur/ur.dart'; +import 'package:mobx/mobx.dart'; +import 'package:ur/ur_decoder.dart'; + +part 'qr_tools_view_model.g.dart'; + +class QRToolsViewModel = QRToolsViewModelBase with _$QRToolsViewModel; + +enum DevQrType { + zpub, + xpub, + bbqr, + bcur, + unknown, +} + +abstract class QRToolsViewModelBase with Store { + @observable + String input = ''; + + + + @computed + Map get data => _getResult(input); + + Uint8List _decodeCBOR(Uint8List cbor) { + final cborDecoder = CBORDecoder(cbor); + final out = cborDecoder.decodeBytes(); + return out.$1; + } + + + Map _getResult(String input) { + try { + final decoder = URDecoder(); + for (var part in input.split('\n')) { + part = part.toLowerCase(); + part = part.trim(); + if (part.startsWith('-')) { + part = part.substring(1).trim(); + } + if (!part.startsWith('ur:')) { + continue; + } + decoder.receivePart(part); + } + return { + "result": (decoder.result != null) ? switch (decoder.result.runtimeType) { + UR => { + "cbor": (decoder.result as UR).cbor, + "_cbor.decode.base64": base64.encode(_decodeCBOR((decoder.result as UR).cbor)), + "type": (decoder.result as UR).type, + "toString": (decoder.result as UR).toString(), + }, + _ => "unknown type: ${decoder.result.runtimeType}" + } : null, + "expectedType": decoder.expectedType, + "estimatedPercentComplete": decoder.estimatedPercentComplete(), + "processedPartsCount": decoder.processedPartsCount(), + "expectedPartCount": decoder.expectedPartCount(), + "isComplete": decoder.isComplete(), + "isFailure": decoder.isFailure(), + "isSuccess": decoder.isSuccess(), + "lastPartIndexes": decoder.lastPartIndexes(), + "receivedPartIndexes": decoder.receivedPartIndexes(), + "resultError": decoder.resultError(), + "resultMessage": decoder.resultMessage(), + "toString": decoder.toString(), + + }; + } catch (e) { + return { + "error": e.toString(), + }; + } + } +} \ No newline at end of file diff --git a/lib/view_model/hardware_wallet/bitbox_view_model.dart b/lib/view_model/hardware_wallet/bitbox_view_model.dart new file mode 100644 index 000000000..20370b58b --- /dev/null +++ b/lib/view_model/hardware_wallet/bitbox_view_model.dart @@ -0,0 +1,110 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/root_dir.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk; +import 'package:mobx/mobx.dart'; + +part 'bitbox_view_model.g.dart'; + +class BitboxViewModel = BitboxViewModelBase with _$BitboxViewModel; + +abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store { + late final sdk.BitboxManager bitboxManager; + + BitboxViewModelBase() { + if (!Platform.isIOS && !isMoneroOnly) { + bitboxManager = sdk.BitboxManager(); + } + } + + @override + @observable + bool isBleEnabled = false; + + @override + bool get hasBluetooth => Platform.isIOS && false; // TODO: remove when we enable bluetooth + + @override + Future updateBleState() async {} + + @override + Stream scanForBleDevices() => throw UnimplementedError(); + + @override + Future> getAllUsbDevices() => bitboxManager.devices + .then((devices) => devices.map((d) => BitboxHardwareWalletDevice(d)).toList()); + + @override + Future stopScanning() async {} + + @override + Future connectDevice(HardwareWalletDevice device, WalletType type) async { + if (!(device is BitboxHardwareWalletDevice)) return false; + if (_isConnecting) return false; + _isConnecting = true; + + try { + final appDocDir = await getAppDir(); + + await bitboxManager.connect(device.device); + printV("Got Connection", file: '${appDocDir.path}/error.txt'); + await bitboxManager.initBitBox(); + printV("Bitbox initialized!", file: '${appDocDir.path}/error.txt'); + await bitboxManager.channelHashVerify(); + printV("Bitbox channel-hash verified!", file: '${appDocDir.path}/error.txt'); + _isConnecting = false; + return true; + } catch (e) { + printV(e); + } + _isConnecting = false; + return false; + } + + bool _isConnecting = false; + + @override + bool get isConnected => false; + + @override + HardwareWalletService getHardwareWalletService(WalletType type) { + switch (type) { + case WalletType.bitcoin: + return bitcoin!.getBitboxHardwareWalletService(bitboxManager, true); + case WalletType.litecoin: + return bitcoin!.getBitboxHardwareWalletService(bitboxManager, false); + case WalletType.ethereum: + return ethereum!.getBitboxHardwareWalletService(bitboxManager); + case WalletType.polygon: + return polygon!.getBitboxHardwareWalletService(bitboxManager); + default: + throw UnimplementedError(); + } + } + + @override + void initWallet(WalletBase wallet) { + switch (wallet.type) { + case WalletType.bitcoin: + case WalletType.litecoin: + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); + case WalletType.ethereum: + return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); + case WalletType.polygon: + return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); + default: + throw Exception('Unexpected wallet type: ${wallet.type}'); + } + } +} diff --git a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart new file mode 100644 index 000000000..825090e02 --- /dev/null +++ b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart @@ -0,0 +1,27 @@ +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; + +abstract class HardwareWalletViewModel { + bool get isConnected; + bool get isBleEnabled; + bool get hasBluetooth; + + Future updateBleState(); + + Stream scanForBleDevices(); + + Future> getAllUsbDevices(); + + Future stopScanning(); + + Future connectDevice(HardwareWalletDevice device, WalletType type); + + HardwareWalletService getHardwareWalletService(WalletType type); + + void initWallet(WalletBase wallet); + + String? interpretErrorCode(String error) => null; +} + diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index be3982d81..7fa01340e 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/main.dart'; @@ -9,10 +10,13 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/hardware/device_connection_type.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/widgets.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk; @@ -23,15 +27,14 @@ part 'ledger_view_model.g.dart'; class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel; -abstract class LedgerViewModelBase with Store { - // late final Ledger ledger; +abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store { late final sdk.LedgerInterface ledgerPlusBLE; late final sdk.LedgerInterface ledgerPlusUSB; bool get _doesSupportHardwareWallets { if (isMoneroOnly) { return DeviceConnectionType.supportedConnectionTypes( - WalletType.monero, Platform.isIOS) + WalletType.monero, HardwareWalletType.ledger, Platform.isIOS) .isNotEmpty; } @@ -40,8 +43,8 @@ abstract class LedgerViewModelBase with Store { LedgerViewModelBase() { if (_doesSupportHardwareWallets) { - reaction((_) => bleIsEnabled, (_) { - if (bleIsEnabled) _initBLE(); + reaction((_) => isBleEnabled, (_) { + if (isBleEnabled) _initBLE(); }); updateBleState(); @@ -51,13 +54,17 @@ abstract class LedgerViewModelBase with Store { } } + @override @observable - bool bleIsEnabled = false; + bool isBleEnabled = false; + + @override + bool get hasBluetooth => true; bool _bleIsInitialized = false; Future _initBLE() async { - if (bleIsEnabled && !_bleIsInitialized) { + if (isBleEnabled && !_bleIsInitialized) { ledgerPlusBLE = sdk.LedgerInterface.ble( onPermissionRequest: (_) async { if (Platform.isMacOS) return true; @@ -76,24 +83,32 @@ abstract class LedgerViewModelBase with Store { } } + @override Future updateBleState() async { final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState(); final newState = bleState == sdk.AvailabilityState.poweredOn; - if (newState != bleIsEnabled) bleIsEnabled = newState; + if (newState != isBleEnabled) isBleEnabled = newState; } - Stream scanForBleDevices() => ledgerPlusBLE.scan(); + @override + Stream scanForBleDevices() => + ledgerPlusBLE.scan().map((d) => LedgerHardwareWalletDevice(d)); - Stream scanForUsbDevices() => ledgerPlusUSB.scan(); + @override + Future> getAllUsbDevices() => ledgerPlusUSB.devices + .then((devices) => devices.map((d) => LedgerHardwareWalletDevice(d)).toList()); + @override Future stopScanning() async { if (_bleIsInitialized) await ledgerPlusBLE.stopScanning(); if (!Platform.isIOS) await ledgerPlusUSB.stopScanning(); } - Future connectLedger(sdk.LedgerDevice device, WalletType type) async { + @override + Future connectDevice(HardwareWalletDevice device, WalletType type) async { + if (!(device is LedgerHardwareWalletDevice)) return false; if (_isConnecting) return false; _isConnecting = true; _connectingWalletType = type; @@ -103,18 +118,18 @@ abstract class LedgerViewModelBase with Store { } catch (_) {} } - final ledger = device.connectionType == sdk.ConnectionType.ble + final ledger = device.connectionType == HardwareWalletConnectionType.ble ? ledgerPlusBLE : ledgerPlusUSB; if (_connectionChangeSubscription == null) { _connectionChangeSubscription = ledger - .deviceStateChanges(device.id) + .deviceStateChanges(device.device.id) .listen(_connectionChangeListener); } try { - _connection = await ledger.connect(device); + _connection = await ledger.connect(device.device); _isConnecting = false; return true; } catch (e) { @@ -140,6 +155,7 @@ abstract class LedgerViewModelBase with Store { Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: WalletType.monero, + hardwareWalletType: HardwareWalletType.ledger, allowChangeWallet: true, isReconnect: true, onConnectDevice: (context, ledgerVM) async { @@ -153,26 +169,47 @@ abstract class LedgerViewModelBase with Store { } } + @override bool get isConnected => _connection != null && !(_connection!.isDisconnected); sdk.LedgerConnection get connection => _connection!; - void setLedger(WalletBase wallet) { + @override + void initWallet(WalletBase wallet) { switch (wallet.type) { case WalletType.monero: return monero!.setLedgerConnection(wallet, connection); case WalletType.bitcoin: + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); case WalletType.litecoin: - return bitcoin!.setLedgerConnection(wallet, connection); + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); case WalletType.ethereum: - return ethereum!.setLedgerConnection(wallet, connection); + return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); case WalletType.polygon: - return polygon!.setLedgerConnection(wallet, connection); + return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } } + @override + HardwareWalletService getHardwareWalletService(WalletType type) { + switch (type) { + case WalletType.bitcoin: + return bitcoin!.getLedgerHardwareWalletService(connection, true); + case WalletType.litecoin: + return bitcoin!.getLedgerHardwareWalletService(connection, false); + case WalletType.ethereum: + return ethereum!.getLedgerHardwareWalletService(connection); + case WalletType.polygon: + return polygon!.getLedgerHardwareWalletService(connection); + default: + throw UnimplementedError(); + } + } + + + @override String? interpretErrorCode(String error) { if (error.contains("Make sure no other program is communicating with the Ledger")) { return error; diff --git a/lib/view_model/restore/restore_wallet.dart b/lib/view_model/restore/restore_wallet.dart index 153a9960a..1ca8dd0cf 100644 --- a/lib/view_model/restore/restore_wallet.dart +++ b/lib/view_model/restore/restore_wallet.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cw_core/utils/zpub.dart'; import 'package:cw_core/wallet_type.dart'; class RestoredWallet { @@ -44,6 +45,9 @@ class RestoredWallet { } catch (e) { // fine, we don't care, it is only for monero anyway } + if (json['zpub'] != null) { + json['xpub'] = convertZpubToXpub(json['zpub'] as String); + } json['view_key'] ??= json['xpub']; final height = json['height'] as String?; return RestoredWallet( diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index 94b1072ab..5fe46da7b 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -58,6 +58,7 @@ class WalletRestoreFromQRCode { final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key)); if (code.startsWith("xpub")) return WalletType.bitcoin; + if (code.startsWith("zpub")) return WalletType.bitcoin; if (extracted == null) { // Special case for view-only monero wallet @@ -109,9 +110,11 @@ class WalletRestoreFromQRCode { if (code == null) throw Exception("Unexpected scan QR code value: aborted"); if (code.isEmpty) throw Exception('Unexpected scan QR code value: value is empty'); + if (code.startsWith("[")) code = code.substring(code.indexOf("]") + 1); + String formattedUri = ''; WalletType? walletType = _extractWalletType(code); - + final prefix = code.startsWith('xpub') ? 'xpub' : code.startsWith('zpub') ? 'zpub' : '????'; if (walletType == null) { await _specifyWalletAssets(context, "Can't determine wallet type, please pick it manually"); walletType = @@ -122,14 +125,14 @@ class WalletRestoreFromQRCode { formattedUri = seedPhrase != null ? '$walletType:?seed=$seedPhrase' - : code.startsWith('xpub') - ? '$walletType:?xpub=$code' + : code.startsWith(prefix) + ? '$walletType:?$prefix=$code' : throw Exception('Failed to determine valid seed phrase'); } else { final index = code.indexOf(':'); final query = code.substring(index + 1).replaceAll('?', '&'); - formattedUri = code.startsWith('xpub') - ? '$walletType:?xpub=$code' + formattedUri = code.startsWith(prefix) + ? '$walletType:?$prefix=$code' :'$walletType:?$query'; } @@ -167,7 +170,8 @@ class WalletRestoreFromQRCode { throw Exception('Unexpected restore mode: tx_payment_id is invalid'); } - if (credentials.containsKey("xpub")) { + if (credentials.containsKey("xpub") || + credentials.containsKey("zpub")) { return WalletRestoreMode.keys; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 2a956a5e2..5e4bdae95 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -36,6 +36,7 @@ import 'package:cake_wallet/tron/tron.dart'; 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/hardware_wallet_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/send/fees_view_model.dart'; import 'package:cake_wallet/view_model/send/output.dart'; @@ -93,7 +94,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor this.balanceViewModel, this.contactListViewModel, this.transactionDescriptionBox, - this.ledgerViewModel, + this.hardwareWalletViewModel, this.unspentCoinsListViewModel, this.feesViewModel, this.walletInfoSource, { @@ -368,7 +369,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor final SendTemplateViewModel sendTemplateViewModel; final BalanceViewModel balanceViewModel; final ContactListViewModel contactListViewModel; - final LedgerViewModel? ledgerViewModel; + final HardwareWalletViewModel? hardwareWalletViewModel; final FeesViewModel feesViewModel; final FiatConversionStore _fiatConversationStore; final Box transactionDescriptionBox; diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 64265a09d..407c03905 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -1,17 +1,15 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; -import 'package:cake_wallet/entities/provider_types.dart'; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/package_info.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart'; -// import 'package:package_info/package_info.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -58,20 +56,28 @@ abstract class OtherSettingsViewModelBase with Store { @computed bool get changeRepresentativeEnabled => - _wallet.type == WalletType.nano || _wallet.type == WalletType.banano; + [WalletType.nano, WalletType.banano].contains(_wallet.type); @computed - bool get displayTransactionPriority => !(changeRepresentativeEnabled || - _wallet.type == WalletType.solana || - _wallet.type == WalletType.tron); + bool get changeHardwareWalletTypeEnabled => [ + HardwareWalletType.cupcake, + HardwareWalletType.coldcard, + HardwareWalletType.seedsigner, + ].contains(_wallet.hardwareWalletType); + + @computed + bool get displayTransactionPriority => + !(changeRepresentativeEnabled || [WalletType.solana, WalletType.tron].contains(_wallet.type)); String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; - if (_wallet.type == WalletType.bitcoin || - _wallet.type == WalletType.litecoin || - _wallet.type == WalletType.bitcoinCash || - _wallet.type == WalletType.dogecoin) { + if ([ + WalletType.bitcoin, + WalletType.litecoin, + WalletType.bitcoinCash, + WalletType.dogecoin, + ].contains(_wallet.type)) { final rate = bitcoin!.getFeeRate(_wallet, _priority); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); } @@ -82,10 +88,12 @@ abstract class OtherSettingsViewModelBase with Store { String getDisplayBitcoinPriority(dynamic priority, int customValue) { final _priority = priority as TransactionPriority; - if (_wallet.type == WalletType.bitcoin || - _wallet.type == WalletType.litecoin || - _wallet.type == WalletType.bitcoinCash || - _wallet.type == WalletType.dogecoin) { + if ([ + WalletType.bitcoin, + WalletType.litecoin, + WalletType.bitcoinCash, + WalletType.dogecoin, + ].contains(_wallet.type)) { final rate = bitcoin!.getFeeRate(_wallet, _priority); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue); @@ -105,6 +113,12 @@ abstract class OtherSettingsViewModelBase with Store { _settingsStore.priority[_wallet.type] = priority; } + @action + Future onHardwareWalletTypeChanged(HardwareWalletType hwType) async { + _wallet.walletInfo.hardwareWalletType = hwType; + return _wallet.walletInfo.save(); + } + @computed double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble(); diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 5e0525ce0..49ee117b4 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -473,7 +473,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet); @computed - bool get isCupcake => + bool get isBitcoinViewOnly => wallet.type == WalletType.bitcoin && (bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isEmpty; diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index 0cfc18c16..8e2e2f7bd 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; @@ -24,12 +25,12 @@ class WalletHardwareRestoreViewModel = WalletHardwareRestoreViewModelBase with _$WalletHardwareRestoreViewModel; abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with Store { - final LedgerViewModel ledgerViewModel; + final HardwareWalletViewModel hardwareWalletVM; int _nextIndex = 0; WalletHardwareRestoreViewModelBase( - this.ledgerViewModel, + this.hardwareWalletVM, AppStore appStore, WalletCreationService walletCreationService, Box walletInfoSource, @@ -56,33 +57,16 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with @action Future getNextAvailableAccounts(int limit) async { try { - List accounts; - switch (type) { - case WalletType.bitcoin: - accounts = await bitcoin! - .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit); - break; - case WalletType.litecoin: - accounts = await bitcoin! - .getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit); - break; - case WalletType.ethereum: - accounts = await ethereum! - .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit); - break; - case WalletType.polygon: - accounts = await polygon! - .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit); - break; - default: - return; - } + List accounts = await hardwareWalletVM + .getHardwareWalletService(type) + .getAvailableAccounts(index: _nextIndex, limit: limit); availableAccounts.addAll(accounts); _nextIndex += limit; } catch (e) { printV(e); - error = ledgerViewModel.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error; + error = + hardwareWalletVM.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error; } isLoadingMoreAccounts = false; @@ -110,7 +94,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials( name: name, - ledgerConnection: ledgerViewModel.connection, + ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection, password: password, height: _options['height'] as int? ?? 0, ); @@ -118,7 +102,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with throw Exception('Unexpected type: ${type.toString()}'); } - credentials.hardwareWalletType = HardwareWalletType.ledger; + if (hardwareWalletVM is LedgerViewModel) + credentials.hardwareWalletType = HardwareWalletType.ledger; + else + credentials.hardwareWalletType = HardwareWalletType.bitbox; return credentials; } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 7e73cb83b..cd10d143e 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -33,7 +33,7 @@ class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreVi abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, Box walletInfoSource, SeedSettingsViewModel seedSettingsViewModel, - {required WalletType type, this.restoredWallet}) + {required WalletType type, this.restoredWallet, this.hardwareWalletType}) : isButtonEnabled = restoredWallet != null, hasPassphrase = false, mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed, @@ -102,6 +102,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { ].contains(type); final RestoredWallet? restoredWallet; + final HardwareWalletType? hardwareWalletType; @observable WalletRestoreMode mode; @@ -226,6 +227,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { name: name, password: password, xpub: viewKey!, + hardwareWalletType: hardwareWalletType, ); case WalletType.monero: diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 20d6aab3f..5a6072b4a 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -116,6 +116,8 @@ dependencies: git: url: https://github.com/vespr-wallet/ledger-flutter-plus ref: 60817d4b20144f9da9029f5034790272795b9d38 + bitbox_flutter: + path: ./scripts/bitbox_flutter hashlib: 1.19.2 on_chain: git: @@ -186,6 +188,10 @@ dependency_overrides: git: url: https://github.com/vespr-wallet/ledger-flutter-plus ref: 60817d4b20144f9da9029f5034790272795b9d38 + ledger_usb_plus: + git: + url: https://github.com/konstantinullrich/ledger-usb-plus + ref: dad482c847c0d0eedceabbb40d3478e64b65e630 socks5_proxy: git: url: https://github.com/LacticWhale/socks_dart.git diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 03c742549..bf05dc594 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -171,6 +171,7 @@ "collection_description": "وصف المجموعة", "collection_name": "اسم المجموعة", "color_theme": "سمة اللون", + "coming_soon_tag": "قريباً", "commit_transaction_amount_fee": "تنفيذ الصفقة\nالمبلغ: ${amount}\nالرسوم: ${fee}", "confirm": "تأكيد", "confirm_delete_template": "سيؤدي هذا الإجراء إلى حذف هذا القالب. هل ترغب في الاستمرار؟", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "حدد جهازك في محفظة كعكة ومتابعة الإعداد.", "connect_yats": "توصيل Yats", "connect_your_hardware_wallet": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth أو USB", - "connect_your_hardware_wallet_ios": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth", + "connect_your_hardware_wallet_ble": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth", + "connect_your_hardware_wallet_usb": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام USB", "connected": "متصل", "connection_sync": "الاتصال والمزامنة", "connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ", @@ -784,6 +786,7 @@ "select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.", "select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ", "select_hw_account_below": "الرجاء تحديد حساب الاستعادة أدناه:", + "select_manufacturer_title": "حدد الشركة المصنعة", "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ", "select_your_country": "الرجاء تحديد بلدك", "selected_trocador_provider": "مزود تروكادور المختار", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "يمكنك العودة إلى DAPP الخاص بك الآن", "your": "لك", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index cf54121a7..b42f18a8f 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -171,6 +171,7 @@ "collection_description": "Описание на колекцията", "collection_name": "Име на колекцията", "color_theme": "Цвят", + "coming_soon_tag": "Очаквайте скоро", "commit_transaction_amount_fee": "Изпълняване на транзакция\nСума: ${amount}\nТакса: ${fee}", "confirm": "Потвърждаване", "confirm_delete_template": "Този шаблон ще бъде изтрит. Искате ли да продължите?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Изберете вашето устройство в портфейла за торта и продължете с настройката.", "connect_yats": "Добавете Yats", "connect_your_hardware_wallet": "Свържете хардуерния си портфейл с помощта на Bluetooth или USB", - "connect_your_hardware_wallet_ios": "Свържете хардуерния си портфейл с помощта на Bluetooth", + "connect_your_hardware_wallet_ble": "Свържете хардуерния си портфейл с помощта на Bluetooth", + "connect_your_hardware_wallet_usb": "Свържете хардуерния си портфейл с помощта на USB", "connected": "Свързани", "connection_sync": "Свързване и синхронизиране", "connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции", @@ -784,6 +786,7 @@ "select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.", "select_destination": "Моля, изберете дестинация за архивния файл.", "select_hw_account_below": "Моля, изберете кой акаунт да възстановите по -долу:", + "select_manufacturer_title": "Изберете производител", "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.", "select_your_country": "Моля, изберете вашата страна", "selected_trocador_provider": "Избран доставчик на трокадор", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "Можете да се върнете при вашия Dapp сега", "your": "Вашия", "yy": "гг" -} \ No newline at end of file +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 54e58dd55..996d23c87 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -171,6 +171,7 @@ "collection_description": "Popis sbírky", "collection_name": "Název sbírky", "color_theme": "Barevný motiv", + "coming_soon_tag": "Přijde brzy", "commit_transaction_amount_fee": "Odeslat transakci\nČástka: ${amount}\nPoplatek: ${fee}", "confirm": "Potvrdit", "confirm_delete_template": "Tato akce smaže tuto šablonu. Přejete si pokračovat?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Vyberte zařízení v peněžence dortu a pokračujte v nastavení.", "connect_yats": "Připojit Yaty", "connect_your_hardware_wallet": "Připojte hardwarovou peněženku pomocí Bluetooth nebo USB", - "connect_your_hardware_wallet_ios": "Připojte hardwarovou peněženku pomocí Bluetooth", + "connect_your_hardware_wallet_ble": "Připojte hardwarovou peněženku pomocí Bluetooth", + "connect_your_hardware_wallet_usb": "Připojte hardwarovou peněženku pomocí USB", "connected": "Připojeno", "connection_sync": "Připojení a synch.", "connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce", @@ -784,6 +786,7 @@ "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.", "select_hw_account_below": "Níže vyberte, který účet chcete obnovit:", + "select_manufacturer_title": "Vyberte výrobce", "select_sell_provider_notice": "Výše vyberte poskytovatele prodeje. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele prodeje v nastavení aplikace.", "select_your_country": "Vyberte prosím svou zemi", "selected_trocador_provider": "Vybraný poskytovatel Trocador", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "Nyní se můžete vrátit do svého dappu", "your": "Vaše", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 415507bd2..f7f3b6368 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -158,7 +158,7 @@ "choose_from_available_options": "Wähle aus verfügbaren Optionen:", "choose_one": "Wähle ein", "choose_qr_code_format": "Wählen Sie das QR-Code-Format", - "choose_qr_code_format_note": "Wählen Sie das richtige Format für Ihre Hardware-Wallet:", + "choose_qr_code_format_note": "Wählen Sie das richtige Format für Ihr Hardware-Wallet:", "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus", "choose_wallet_currency": "Bitte wählen Sie die Währung der Wallet:", "choose_wallet_group": "Wählen Sie Walletgruppe", @@ -171,6 +171,7 @@ "collection_description": "Sammlung Beschreibung", "collection_name": "Sammlungsname", "color_theme": "Farbthema", + "coming_soon_tag": "Kommt bald", "commit_transaction_amount_fee": "Transaktion absenden\nBetrag: ${amount}\nGebühr: ${fee}", "confirm": "Bestätigen", "confirm_delete_template": "Diese Aktion löscht diese Vorlage. Möchten Sie fortfahren?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Wählen Sie Ihr Gerät in Cake Wallet aus und fahren Sie mit dem Setup fort.", "connect_yats": "Yats verbinden", "connect_your_hardware_wallet": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth oder USB", - "connect_your_hardware_wallet_ios": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth", + "connect_your_hardware_wallet_ble": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth", + "connect_your_hardware_wallet_usb": "Schließen Sie Ihre Hardware-Wallet mit USB an", "connected": "Verbunden", "connection_sync": "Verbindung und Synchronisierung", "connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen", @@ -354,7 +356,7 @@ "error_while_processing": "Ein Fehler beim Proceessing trat ein Fehler auf", "errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen", "errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten", - "establishing_tor_connection": "Einstellung der TOR-Verbindung", + "establishing_tor_connection": "Einstellung der Tor-Verbindung", "estimated": "Geschätzt", "estimated_new_fee": "Geschätzte neue Gebühr", "estimated_receive_amount": "Geschätzter Empfangsbetrag", @@ -646,8 +648,8 @@ "purchase_gift_card": "Geschenkkarte kaufen", "purple_dark_theme": "Lila dunkle Thema", "qr_code_format": "QR-Codeformat", - "qr_code_format_body": "BCUR (Blockchain Commons Uniform Resources) ist der Branchenstandard und am weitesten verbreitet mit Bitcoin-Hardware-Wallet und sollte von den meisten Benutzern ausgewählt werden. BBQR (Better Bitcoin QR) ist ein einzigartiges Format, das nur von Coldcard-Hardware-Wallet verwendet wird, beginnend mit ihrem Modell Q.", - "qr_code_format_note": "Unterschiedliche QR-Codeformate werden von verschiedenen Hardware-Wallet im Bitcoin-Bereich verwendet", + "qr_code_format_body": "BCUR (Blockchain Commons Uniform Resources) ist der Branchenstandard und am weitesten verbreitet mit Bitcoin-Hardware-Wallets und sollte von den meisten Benutzern ausgewählt werden. BBQR (Better Bitcoin QR) ist ein einzigartiges Format, das nur von Coldcard-Hardware-Wallets verwendet wird, beginnend mit ihrem Modell Q.", + "qr_code_format_note": "Unterschiedliche QR-Codeformate werden von verschiedenen Hardware-Wallets im Bitcoin-Bereich verwendet", "qr_fullscreen": "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen", "qr_instruction": "Verwenden Sie diese Adresse, um ein Token oder ein Sammlerstück zu erhalten", "qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?", @@ -785,6 +787,7 @@ "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.", "select_hw_account_below": "Bitte wählen Sie unten, welches Konto unten wiederhergestellt werden soll:", + "select_manufacturer_title": "Hersteller auswählen", "select_sell_provider_notice": "Wählen Sie oben einen Verkaufsanbieter aus. Sie können diesen Bildschirm überspringen, indem Sie in den App-Einstellungen Ihren Standard-Verkaufsanbieter festlegen.", "select_your_country": "Bitte wählen Sie Ihr Land aus", "selected_trocador_provider": "Ausgewählter Trocador-Anbieter", @@ -894,7 +897,7 @@ "sort_by": "Sortiere nach", "spend_key_private": "Spend Key (geheim)", "spend_key_public": "Spend Key (öffentlich)", - "starting_tor_proxy": "TOR-Proxy beginnen", + "starting_tor_proxy": "Tor-Proxy beginnen", "status": "Status: ", "step": "Schritt", "string_default": "Standard", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e7d177591..bcac1484f 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -171,6 +171,7 @@ "collection_description": "Collection Description", "collection_name": "Collection Name", "color_theme": "Color theme", + "coming_soon_tag": "Coming Soon", "commit_transaction_amount_fee": "Commit transaction\nAmount: ${amount}\nFee: ${fee}", "confirm": "Confirm", "confirm_delete_template": "This action will delete this template. Do you wish to continue?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Select your device in Cake Wallet and continue with the setup.", "connect_yats": "Connect Yats", "connect_your_hardware_wallet": "Connect your hardware wallet using Bluetooth or USB", - "connect_your_hardware_wallet_ios": "Connect your hardware wallet using Bluetooth", + "connect_your_hardware_wallet_ble": "Connect your hardware wallet using Bluetooth", + "connect_your_hardware_wallet_usb": "Connect your hardware wallet using USB", "connected": "Connected", "connection_sync": "Connection and sync", "connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions", @@ -785,6 +787,7 @@ "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.", "select_hw_account_below": "Please select which account to restore below:", + "select_manufacturer_title": "Select Manufacturer", "select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.", "select_your_country": "Please select your country", "selected_trocador_provider": "selected Trocador provider", @@ -1147,4 +1150,4 @@ "youCanGoBackToYourDapp": "You can go back to your dApp now", "your": "Your", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 6635599ea..31cd806b8 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -171,6 +171,7 @@ "collection_description": "Descripción de la colección", "collection_name": "Nombre de colección", "color_theme": "Tema de color", + "coming_soon_tag": "Muy pronto", "commit_transaction_amount_fee": "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}", "confirm": "Confirmar", "confirm_delete_template": "Esta acción eliminará esta plantilla. ¿Deseas continuar?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Seleccione su dispositivo en Cake Wallet y continúe con la configuración.", "connect_yats": "Conectar Yats", "connect_your_hardware_wallet": "Conecta tu billetera de hardware con Bluetooth o USB", - "connect_your_hardware_wallet_ios": "Conecta tu billetera de hardware con Bluetooth", + "connect_your_hardware_wallet_ble": "Conecta tu billetera de hardware con Bluetooth", + "connect_your_hardware_wallet_usb": "Conecte su billetera de hardware con USB", "connected": "Conectado", "connection_sync": "Conexión y sincronización", "connectWalletPrompt": "Conecte tu billetera con WalletConnect para realizar transacciones", @@ -785,6 +787,7 @@ "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.", "select_hw_account_below": "Seleccione qué cuenta restaurar a continuación:", + "select_manufacturer_title": "Seleccionar fabricante", "select_sell_provider_notice": "Selecciona un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.", "select_your_country": "Seleccione su país", "selected_trocador_provider": "Proveedor de Trocador seleccionado", @@ -1148,4 +1151,4 @@ "youCanGoBackToYourDapp": "Puedes volver a tu dapp ahora", "your": "Su", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index cd4fb1a6a..6cdfb8502 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -171,6 +171,7 @@ "collection_description": "Description de la collection", "collection_name": "Nom de collection", "color_theme": "Couleur du thème", + "coming_soon_tag": "À venir", "commit_transaction_amount_fee": "Valider la transaction\nMontant : ${amount}\nFrais : ${fee}", "confirm": "Confirmer", "confirm_delete_template": "Cette action va supprimer ce modèle. Souhaitez-vous continuer ?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Sélectionnez votre appareil dans Cake Wallet et poursuivez la configuration.", "connect_yats": "Connecter Yats", "connect_your_hardware_wallet": "Connectez votre portefeuille matériel à l'aide du Bluetooth ou de l'USB", - "connect_your_hardware_wallet_ios": "Connectez votre portefeuille matériel à l'aide du Bluetooth", + "connect_your_hardware_wallet_ble": "Connectez votre portefeuille matériel à l'aide du Bluetooth", + "connect_your_hardware_wallet_usb": "Connectez votre portefeuille matériel à l'aide USB", "connected": "Connecté", "connection_sync": "Connexion et synchronisation", "connectWalletPrompt": "Connectez votre portefeuille (wallet) avec WalletConnect pour effectuer des transactions", @@ -784,6 +786,7 @@ "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.", "select_hw_account_below": "Veuillez sélectionner le compte à restaurer ci-dessous:", + "select_manufacturer_title": "Sélectionner le fabricant", "select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.", "select_your_country": "Veuillez sélectionner votre pays", "selected_trocador_provider": "fournisseur de trocador sélectionné", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "Vous pouvez retourner à votre Dapp maintenant", "your": "Ton", "yy": "AA" -} \ No newline at end of file +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 784052a18..a492d4923 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -171,6 +171,7 @@ "collection_description": "Bayanin tarin", "collection_name": "Sunan tattara", "color_theme": "Jigon launi", + "coming_soon_tag": "Zuwan nan da sannu", "commit_transaction_amount_fee": "Aikata ciniki\nAdadi: ${amount}\nKuda: ${fee}", "confirm": "Tabbatar", "confirm_delete_template": "Wannan aikin zai share wannan samfuri. Kuna so ku ci gaba?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Zaɓi na'urarka a cikin walat walat kuma ci gaba da saiti.", "connect_yats": "Haɗa Yats", "connect_your_hardware_wallet": "Haɗa Wallake Wallware ɗinku ta Bluetooth ko USB", - "connect_your_hardware_wallet_ios": "Haɗa kayan aikinku ta Bluetooth", + "connect_your_hardware_wallet_ble": "Haɗa kayan aikinku ta Bluetooth", + "connect_your_hardware_wallet_usb": "Haɗa kayan aikinku ta amfani da USB", "connected": "Wanda aka haɗa", "connection_sync": "Haɗi da daidaitawa", "connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala", @@ -786,6 +788,7 @@ "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.", "select_hw_account_below": "Da fatan za a zabi wanda asusun zai gyara a ƙasa:", + "select_manufacturer_title": "Select Manaser", "select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.", "select_your_country": "Da fatan za a zabi ƙasarku", "selected_trocador_provider": "Zabi mai bada TORACAD", @@ -1149,4 +1152,4 @@ "youCanGoBackToYourDapp": "Kuna iya komawa zuwa DPP ɗinku yanzu", "your": "Naku", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 964e2aff0..5a94c1fe5 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -171,6 +171,7 @@ "collection_description": "संग्रह विवरण", "collection_name": "संग्रह नाम", "color_theme": "रंग विषय", + "coming_soon_tag": "जल्द आ रहा है", "commit_transaction_amount_fee": "लेन-देन करें\nरकम: ${amount}\nशुल्क: ${fee}", "confirm": "की पुष्टि करें", "confirm_delete_template": "यह क्रिया इस टेम्पलेट को हटा देगी। क्या आप जारी रखना चाहते हैं?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "केक वॉलेट में अपने डिवाइस का चयन करें और सेटअप के साथ जारी रखें।", "connect_yats": "कनेक्ट Yats", "connect_your_hardware_wallet": "ब्लूटूथ या यूएसबी का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें", - "connect_your_hardware_wallet_ios": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें", + "connect_your_hardware_wallet_ble": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें", + "connect_your_hardware_wallet_usb": "USB का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें", "connected": "जुड़े हुए", "connection_sync": "कनेक्शन और सिंक", "connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें", @@ -604,8 +606,8 @@ "payjoin_unavailable_sheet_title": "Payjoin अनुपलब्ध क्यों है?", "payment_id": "भुगतान ID: ", "payment_made_easy": "भुगतान आसान किया गया", - "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।", + "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "payments": "भुगतान", "pending": " (अपूर्ण)", "percentageOf": "${amount} का", @@ -786,6 +788,7 @@ "select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।", "select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।", "select_hw_account_below": "कृपया नीचे पुनर्स्थापित करने के लिए कौन सा खाता चुनें:", + "select_manufacturer_title": "निर्माता का चयन करें", "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।", "select_your_country": "कृपया अपने देश का चयन करें", "selected_trocador_provider": "चयनित ट्रोकैडर प्रदाता", @@ -1148,4 +1151,4 @@ "youCanGoBackToYourDapp": "अब आप अपने DAPP पर वापस जा सकते हैं", "your": "आपका", "yy": "वाईवाई" -} \ No newline at end of file +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index ecfd139e7..d3db2ade7 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -171,6 +171,7 @@ "collection_description": "Zbirka Opis opisa", "collection_name": "Naziv kolekcije", "color_theme": "Shema boja", + "coming_soon_tag": "Dolazi uskoro", "commit_transaction_amount_fee": "Izvrši transakciju \nAmount: ${amount}\nFee: ${fee}", "confirm": "Potvrdi", "confirm_delete_template": "Ovom ćete radnjom izbrisati ovaj predložak. Želite li nastaviti?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Odaberite svoj uređaj u novčanici za torte i nastavite s postavljanjem.", "connect_yats": "Povežite Yats", "connect_your_hardware_wallet": "Spojite svoj hardverski novčanik pomoću Bluetooth -a ili USB -a", - "connect_your_hardware_wallet_ios": "Spojite svoj hardverski novčanik pomoću Bluetooth -a", + "connect_your_hardware_wallet_ble": "Spojite svoj hardverski novčanik pomoću Bluetooth -a", + "connect_your_hardware_wallet_usb": "Spojite svoj hardverski novčanik pomoću USB -a", "connected": "Povezan", "connection_sync": "Povezivanje i sinkronizacija", "connectWalletPrompt": "Povežite svoj novčanik s WalletConnectom za obavljanje transakcija", @@ -784,6 +786,7 @@ "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.", "select_hw_account_below": "Molimo odaberite koji će se račun vratiti u nastavku:", + "select_manufacturer_title": "Odaberite proizvođača", "select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.", "select_your_country": "Odaberite svoju zemlju", "selected_trocador_provider": "Odabrani pružatelj usluga trokadora", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "Sada se možete vratiti na svoj dapp", "your": "Vaš", "yy": "GG" -} \ No newline at end of file +} diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 10156ab20..577fe0b32 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -171,6 +171,7 @@ "collection_description": "Հավաքածուի նկարագրությունը", "collection_name": "Հավաքածուի անուն", "color_theme": "Գույների տեսք", + "coming_soon_tag": "Շուտով", "commit_transaction_amount_fee": "Հաստատել գործարքը\nՍկզբնական գումար. ${amount}\nՄիջնորդավճար. ${fee}", "confirm": "Հաստատել", "confirm_delete_template": "Այս գործողությունը կջնջի այս ձևանմուշը: Ցանկանու՞մ եք շարունակել։", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Ընտրեք ձեր սարքը տորթի դրամապանակում եւ շարունակեք կարգաբերումը:", "connect_yats": "Միացրեք Yat-ները", "connect_your_hardware_wallet": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի կամ USB-ի միջոցով", - "connect_your_hardware_wallet_ios": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի միջոցով", + "connect_your_hardware_wallet_ble": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի միջոցով", + "connect_your_hardware_wallet_usb": "Միացրեք ձեր ապարատային դրամապանակը `օգտագործելով USB", "connected": "Միացված", "connection_sync": "Կապ և սինխրոնիզացիա", "connectWalletPrompt": "Միացրեք ձեր դրամապանակը WalletConnect-ի միջոցով գործարքներ կատարելու համար", @@ -783,6 +785,7 @@ "select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով", "select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը", "select_hw_account_below": "Խնդրում ենք ընտրել, թե որ հաշիվն է վերականգնել հետեւյալը.", + "select_manufacturer_title": "Ընտրեք արտադրող", "select_sell_provider_notice": "Ընտրեք վաճառքի մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն վաճառքի մատակարարը հավելվածի կարգավորումներում սահմանելով", "select_your_country": "Խնդրում ենք ընտրել ձեր երկիրը", "sell": "Ծախել", @@ -1144,4 +1147,4 @@ "youCanGoBackToYourDapp": "Այժմ կարող եք վերադառնալ ձեր DAPP- ին", "your": "Քո", "yy": "ՏՏ" -} \ No newline at end of file +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 31afd9378..7165a49e4 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -171,6 +171,7 @@ "collection_description": "Deskripsi Koleksi", "collection_name": "Nama Koleksi", "color_theme": "Tema warna", + "coming_soon_tag": "Segera hadir", "commit_transaction_amount_fee": "Lakukan transaksi\nJumlah: ${amount}\nBiaya: ${fee}", "confirm": "Konfirmasi", "confirm_delete_template": "Tindakan ini akan menghapus template ini. Apakah Anda ingin melanjutkan?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Pilih perangkat Anda di dompet kue dan lanjutkan dengan pengaturan.", "connect_yats": "Hubungkan Yats", "connect_your_hardware_wallet": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth atau USB", - "connect_your_hardware_wallet_ios": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth", + "connect_your_hardware_wallet_ble": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth", + "connect_your_hardware_wallet_usb": "Hubungkan dompet perangkat keras Anda menggunakan USB", "connected": "Terhubung", "connection_sync": "Koneksi dan sinkronisasi", "connectWalletPrompt": "Hubungkan dompet Anda dengan WalletConnect untuk melakukan transaksi", @@ -787,6 +789,7 @@ "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.", "select_hw_account_below": "Pilih akun mana yang akan dikembalikan di bawah ini:", + "select_manufacturer_title": "Pilih produsen", "select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.", "select_your_country": "Pilih negara Anda", "selected_trocador_provider": "Penyedia Trocador Terpilih", @@ -1149,4 +1152,4 @@ "youCanGoBackToYourDapp": "Anda dapat kembali ke dapp Anda sekarang", "your": "Milikmu", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index cd0225e37..40c88b2cf 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -171,6 +171,7 @@ "collection_description": "Descrizione della raccolta", "collection_name": "Nome della raccolta", "color_theme": "Colore tema", + "coming_soon_tag": "Prossimamente", "commit_transaction_amount_fee": "Invia transazione\nAmmontare: ${amount}\nCommissione: ${fee}", "confirm": "Conferma", "confirm_delete_template": "Questa azione cancellerà questo modello. Desideri continuare?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Seleziona il tuo dispositivo nel portafoglio Cake e continua con l'installazione.", "connect_yats": "Connetti Yats", "connect_your_hardware_wallet": "Collega il tuo portafoglio hardware tramite Bluetooth o USB", - "connect_your_hardware_wallet_ios": "Collega il tuo portafoglio hardware tramite Bluetooth", + "connect_your_hardware_wallet_ble": "Collega il tuo portafoglio hardware tramite Bluetooth", + "connect_your_hardware_wallet_usb": "Collega il tuo portafoglio hardware usando USB", "connected": "Collegato", "connection_sync": "Connessione e sincronizzazione", "connectWalletPrompt": "Collega il tuo portafoglio con WalletConnect per effettuare transazioni", @@ -785,6 +787,7 @@ "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.", "select_hw_account_below": "Seleziona quale account ripristinare:", + "select_manufacturer_title": "Seleziona il produttore", "select_sell_provider_notice": "Seleziona un provider di vendita sopra. Puoi saltare questa schermata impostando il tuo provider di vendita predefinito nelle impostazioni dell'app.", "select_your_country": "Seleziona il tuo paese", "selected_trocador_provider": "Provider di Trocador selezionato", @@ -1149,4 +1152,4 @@ "youCanGoBackToYourDapp": "Puoi tornare al tuo DApp ora", "your": "Tuo", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index bbb9bedb3..55f4d4dae 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -171,6 +171,7 @@ "collection_description": "コレクションの説明", "collection_name": "コレクション名", "color_theme": "カラーテーマ", + "coming_soon_tag": "近日公開", "commit_transaction_amount_fee": "トランザクションをコミット\n量: ${amount}\n費用: ${fee}", "confirm": "確認する", "confirm_delete_template": "この操作により、このテンプレートが削除されます。 続行しますか?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "ケーキウォレットでデバイスを選択し、セットアップを続行します。", "connect_yats": "Yatsを接続します", "connect_your_hardware_wallet": "BluetoothまたはUSBを使用して、ハードウェアウォレットを接続します", - "connect_your_hardware_wallet_ios": "Bluetoothを使用してハードウェアウォレットを接続します", + "connect_your_hardware_wallet_ble": "Bluetoothを使用してハードウェアウォレットを接続します", + "connect_your_hardware_wallet_usb": "USBを使用してハードウェアウォレットを接続します", "connected": "接続", "connection_sync": "接続と同期", "connectWalletPrompt": "ウォレットを WalletConnect に接続して取引を行う", @@ -785,6 +787,7 @@ "select_buy_provider_notice": "上記の購入プロバイダーを選択してください。デフォルトの購入プロバイダーをアプリ設定で設定して、この画面をスキップできます。", "select_destination": "バックアップファイルの保存先を選択してください。", "select_hw_account_below": "以下に復元するアカウントを選択してください。", + "select_manufacturer_title": "メーカーを選択します", "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。", "select_your_country": "あなたの国を選択してください", "selected_trocador_provider": "選択したTrocadorプロバイダー", @@ -1147,4 +1150,4 @@ "youCanGoBackToYourDapp": "あなたは今あなたのダップに戻ることができます", "your": "あなたの", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 29c05a188..fbfa21f2e 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -171,6 +171,7 @@ "collection_description": "컬렉션 설명", "collection_name": "컬렉션 이름", "color_theme": "색상 테마", + "coming_soon_tag": "곧 올 것입니다", "commit_transaction_amount_fee": "트랜잭션 커밋\n금액: ${amount}\n수수료: ${fee}", "confirm": "확인", "confirm_delete_template": "이 작업은 이 템플릿을 삭제합니다. 계속하시겠습니까?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "케이크 지갑에서 장치를 선택하고 설정을 계속하십시오.", "connect_yats": "Yat 연결", "connect_your_hardware_wallet": "블루투스 또는 USB를 사용하여 하드웨어 지갑 연결", - "connect_your_hardware_wallet_ios": "블루투스를 사용하여 하드웨어 지갑 연결", + "connect_your_hardware_wallet_ble": "블루투스를 사용하여 하드웨어 지갑 연결", + "connect_your_hardware_wallet_usb": "USB를 사용하여 하드웨어 지갑을 연결하십시오", "connected": "연결됨", "connection_sync": "연결 및 동기화", "connectWalletPrompt": "거래를 하려면 WalletConnect로 지갑을 연결하세요", @@ -785,6 +787,7 @@ "select_buy_provider_notice": "위에서 구매 제공 업체를 선택하세요. 앱 설정에서 기본 구매 제공 업체를 설정하여 이 화면을 건너뛸 수 있습니다.", "select_destination": "백업 파일의 대상을 선택하세요.", "select_hw_account_below": "아래에서 복구할 계정을 선택하세요:", + "select_manufacturer_title": "제조업체를 선택하십시오", "select_sell_provider_notice": "위에서 판매 제공 업체를 선택하세요. 앱 설정에서 기본 판매 제공 업체를 설정하여 이 화면을 건너뛸 수 있습니다.", "select_your_country": "국가를 선택하세요", "selected_trocador_provider": "선택된 Trocador 제공 업체", @@ -1147,4 +1150,4 @@ "youCanGoBackToYourDapp": "이제 dApp으로 돌아갈 수 있습니다", "your": "당신의", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 12329fae3..a93c5c5df 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -171,6 +171,7 @@ "collection_description": "စုဆောင်းခြင်းဖော်ပြချက်", "collection_name": "စုဆောင်းခြင်းအမည်", "color_theme": "အရောင်အပြင်အဆင်", + "coming_soon_tag": "မကြာမီလာမည်", "commit_transaction_amount_fee": "ငွေလွှဲခြင်း\nပမာဏ- ${amount}\nအခကြေးငွေ- ${fee}", "confirm": "အတည်ပြုပါ။", "confirm_delete_template": "ဤလုပ်ဆောင်ချက်သည် ဤပုံစံပြားကို ဖျက်လိုက်ပါမည်။ ဆက်လုပ်လိုပါသလား။", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Cake Wallet တွင်သင်၏ device ကိုရွေးချယ်ပြီး setup ကိုဆက်လုပ်ပါ။", "connect_yats": "Yats ကိုချိတ်ဆက်ပါ။", "connect_your_hardware_wallet": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth သို့မဟုတ် USB ကို သုံး. ချိတ်ဆက်ပါ", - "connect_your_hardware_wallet_ios": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth ကို အသုံးပြု. ချိတ်ဆက်ပါ", + "connect_your_hardware_wallet_ble": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth ကို အသုံးပြု. ချိတ်ဆက်ပါ", + "connect_your_hardware_wallet_usb": "USB သုံး. သင်၏ hardware ပိုက်ဆံအိတ်ကိုချိတ်ဆက်ပါ", "connected": "ချိတ်ဆက်ထားသော", "connection_sync": "ချိတ်ဆက်မှုနှင့် ထပ်တူပြုခြင်း။", "connectWalletPrompt": "အရောင်းအဝယ်ပြုလုပ်ရန် သင့်ပိုက်ဆံအိတ်ကို WalletConnect နှင့် ချိတ်ဆက်ပါ။", @@ -784,6 +786,7 @@ "select_buy_provider_notice": "အပေါ်ကဝယ်သူတစ် ဦး ကိုရွေးချယ်ပါ။ သင်၏ default 0 ယ်သူအား app settings တွင် setting လုပ်ခြင်းဖြင့်ဤ screen ကိုကျော်သွားနိုင်သည်။", "select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။", "select_hw_account_below": "အောက်ဖော်ပြပါမည်သည့်အကောင့်ကိုရွေးပါ။", + "select_manufacturer_title": "ထုတ်လုပ်သူကိုရွေးချယ်ပါ", "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။", "select_your_country": "ကျေးဇူးပြု. သင့်နိုင်ငံကိုရွေးချယ်ပါ", "selected_trocador_provider": "ရွေးချယ်ထား Tracador ပံ့ပိုးပေး", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "သငျသညျယခုသင်၏ dapp ကိုပြန်သွားနိုင်ပါတယ်", "your": "ခင်ဗျားတို့၏", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 2a4e34c90..1322cbb02 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -171,6 +171,7 @@ "collection_description": "Verzamelingsbeschrijving", "collection_name": "Verzamelnaam", "color_theme": "Kleur thema", + "coming_soon_tag": "Binnenkort", "commit_transaction_amount_fee": "Verricht transactie\nBedrag: ${amount}\nhonorarium: ${fee}", "confirm": "Bevestigen", "confirm_delete_template": "Met deze actie wordt deze sjabloon verwijderd. Wilt u doorgaan?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Selecteer uw apparaat in cake -portemonnee en ga verder met de opstelling.", "connect_yats": "Verbind Yats", "connect_your_hardware_wallet": "Sluit uw hardware -portemonnee aan met Bluetooth of USB", - "connect_your_hardware_wallet_ios": "Sluit uw hardware -portemonnee aan met Bluetooth", + "connect_your_hardware_wallet_ble": "Sluit uw hardware -portemonnee aan met Bluetooth", + "connect_your_hardware_wallet_usb": "Sluit uw hardware -portemonnee aan met USB", "connected": "Aangesloten", "connection_sync": "Verbinding en synchronisatie", "connectWalletPrompt": "Verbind uw portemonnee met WalletConnect om transacties uit te voeren", @@ -784,6 +786,7 @@ "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.", "select_hw_account_below": "Selecteer welk account u hieronder moet herstellen:", + "select_manufacturer_title": "Selecteer fabrikant", "select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.", "select_your_country": "Selecteer uw land", "selected_trocador_provider": "Geselecteerde Trocador -provider", @@ -1147,4 +1150,4 @@ "youCanGoBackToYourDapp": "U kunt nu terug naar uw DApp gaan", "your": "Jouw", "yy": "JJ" -} \ No newline at end of file +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 9222d870e..ee6583af7 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -171,6 +171,7 @@ "collection_description": "Opis kolekcji", "collection_name": "Nazwa kolekcji", "color_theme": "Motyw kolorystyczny", + "coming_soon_tag": "Już wkrótce", "commit_transaction_amount_fee": "Zatwierdź transakcję\nIlość: ${amount}\nOpłata: ${fee}", "confirm": "Potwierdź", "confirm_delete_template": "Ta czynność usunie ten szablon. Czy chcesz kontynuować?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Wybierz urządzenie w portfelu ciasta i kontynuuj konfigurację.", "connect_yats": "Połącz Yats", "connect_your_hardware_wallet": "Podłącz portfel sprzętowy za pomocą Bluetooth lub USB", - "connect_your_hardware_wallet_ios": "Podłącz portfel sprzętowy za pomocą Bluetooth", + "connect_your_hardware_wallet_ble": "Podłącz portfel sprzętowy za pomocą Bluetooth", + "connect_your_hardware_wallet_usb": "Podłącz portfel sprzętowy za pomocą USB", "connected": "Połączony", "connection_sync": "Połączenie i synchronizacja", "connectWalletPrompt": "Połącz swój portfel z WalletConnect, aby dokonywać transakcji", @@ -784,6 +786,7 @@ "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.", "select_hw_account_below": "Wybierz, które konto przywrócić poniżej:", + "select_manufacturer_title": "Wybierz producent", "select_sell_provider_notice": "Wybierz dostawcę sprzedaży powyżej. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę sprzedaży w ustawieniach aplikacji.", "select_your_country": "Wybierz swój kraj", "selected_trocador_provider": "Wybrany dostawca Trocador", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "Możesz teraz wrócić do swojego dapp", "your": "Twój", "yy": "RR" -} \ No newline at end of file +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 278bfd90d..1ce58e421 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -171,6 +171,7 @@ "collection_description": "Descrição da coleção", "collection_name": "Nome da coleção", "color_theme": "Tema de cor", + "coming_soon_tag": "Em breve", "commit_transaction_amount_fee": "Confirmar transação\nQuantia: ${amount}\nTaxa: ${fee}", "confirm": "Confirmar", "confirm_delete_template": "Esta ação excluirá este modelo. Você deseja continuar?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Selecione seu dispositivo na Cake Wallet e continue com a configuração.", "connect_yats": "Connect Yats", "connect_your_hardware_wallet": "Conecte sua carteira de hardware usando Bluetooth ou USB", - "connect_your_hardware_wallet_ios": "Conecte sua carteira de hardware usando o Bluetooth", + "connect_your_hardware_wallet_ble": "Conecte sua carteira de hardware usando o Bluetooth", + "connect_your_hardware_wallet_usb": "Conecte sua carteira de hardware usando USB", "connected": "Conectado", "connection_sync": "Conexão e sincronização", "connectWalletPrompt": "Conecte sua carteira ao WalletConnect para fazer transações", @@ -786,6 +788,7 @@ "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.", "select_hw_account_below": "Selecione qual conta para restaurar abaixo:", + "select_manufacturer_title": "Selecione o fabricante", "select_sell_provider_notice": "Selecione um fornecedor de venda acima. Você pode pular esta tela definindo seu provedor de venda padrão nas configurações do aplicativo.", "select_your_country": "Selecione seu país", "selected_trocador_provider": "Provedor de Trocador selecionado", @@ -1149,4 +1152,4 @@ "youCanGoBackToYourDapp": "Você pode voltar para o seu dapp agora", "your": "Seu", "yy": "aa" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 848e927a2..9ac6d852a 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -171,6 +171,7 @@ "collection_description": "Описание коллекции", "collection_name": "Название коллекции", "color_theme": "Цветовая тема", + "coming_soon_tag": "Вскоре", "commit_transaction_amount_fee": "Подтвердить транзакцию \nСумма: ${amount}\nКомиссия: ${fee}", "confirm": "Подтвердить", "confirm_delete_template": "Это действие удалит шаблон. Вы хотите продолжить?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Выберите свое устройство в кошельке для торта и продолжайте с настройкой.", "connect_yats": "Подключить Yats", "connect_your_hardware_wallet": "Подключите свой аппаратный кошелек с помощью Bluetooth или USB", - "connect_your_hardware_wallet_ios": "Подключите свой аппаратный кошелек с помощью Bluetooth", + "connect_your_hardware_wallet_ble": "Подключите свой аппаратный кошелек с помощью Bluetooth", + "connect_your_hardware_wallet_usb": "Подключите свой аппаратный кошелек с помощью USB", "connected": "Подключенный", "connection_sync": "Подключение и синхронизация", "connectWalletPrompt": "Подключите свой кошелек к WalletConnect для совершения транзакций.", @@ -785,6 +787,7 @@ "select_buy_provider_notice": "Выберите поставщика покупки выше. Вы можете пропустить этот экран, установив поставщика покупки по умолчанию в настройках приложения.", "select_destination": "Пожалуйста, выберите место для файла резервной копии.", "select_hw_account_below": "Пожалуйста, выберите, какую учетную запись восстановить ниже:", + "select_manufacturer_title": "Выберите производителя", "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.", "select_your_country": "Пожалуйста, выберите свою страну", "selected_trocador_provider": "Выбранный провайдер Трокадора", @@ -1147,4 +1150,4 @@ "youCanGoBackToYourDapp": "Вы можете вернуться к своему даппу сейчас", "your": "Твой", "yy": "ГГ" -} \ No newline at end of file +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 3f30d6f23..4c8a91681 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -171,6 +171,7 @@ "collection_description": "คำอธิบายการรวบรวม", "collection_name": "ชื่อคอลเลกชัน", "color_theme": "ธีมสี", + "coming_soon_tag": "เร็วๆ นี้", "commit_transaction_amount_fee": "ยืนยันธุรกรรม\nจำนวน: ${amount}\nค่าธรรมเนียม: ${fee}", "confirm": "ยืนยัน", "confirm_delete_template": "การดำเนินการนี้จะลบแบบฟอร์มนี้ คุณต้องการดำเนินการต่อหรือไม่?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "เลือกอุปกรณ์ของคุณในกระเป๋าเงินเค้กและดำเนินการติดตั้งต่อไป", "connect_yats": "เชื่อมต่อ Yats", "connect_your_hardware_wallet": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ หรือ USB", - "connect_your_hardware_wallet_ios": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ", + "connect_your_hardware_wallet_ble": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ", + "connect_your_hardware_wallet_usb": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้ USB", "connected": "ซึ่งเชื่อมต่อกัน", "connection_sync": "การเชื่อมต่อและการซิงค์", "connectWalletPrompt": "เชื่อมต่อกระเป๋าเงินของคุณด้วย WalletConnect เพื่อทำธุรกรรม", @@ -784,6 +786,7 @@ "select_buy_provider_notice": "เลือกผู้ให้บริการซื้อด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการซื้อเริ่มต้นในการตั้งค่าแอป", "select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง", "select_hw_account_below": "กรุณาเลือกบัญชีที่จะกู้คืนด้านล่าง:", + "select_manufacturer_title": "เลือกผู้ผลิต", "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป", "select_your_country": "กรุณาเลือกประเทศของคุณ", "selected_trocador_provider": "ผู้ให้บริการ Trocador ที่เลือก", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "คุณสามารถกลับไปที่ dapp ของคุณได้ทันที", "your": "ของคุณ", "yy": "ปี" -} \ No newline at end of file +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index e832e159a..fc7a1a841 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -171,6 +171,7 @@ "collection_description": "Paglalarawan ng Koleksyon", "collection_name": "Pangalan ng Koleksyon", "color_theme": "Color theme", + "coming_soon_tag": "Malapit na", "commit_transaction_amount_fee": "Gumawa ng transaksyon\nHalaga: ${amount}\nFee: ${fee}", "confirm": "Kumpirmahin", "confirm_delete_template": "Tatanggalin ng pagkilos na ito ang template na ito. Gusto mo bang magpatuloy?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Piliin ang iyong aparato sa cake wallet at magpatuloy sa pag -setup.", "connect_yats": "Ikonekta sa Yats", "connect_your_hardware_wallet": "Ikonekta ang iyong hardware wallet gamit ang Bluetooth o USB", - "connect_your_hardware_wallet_ios": "Ikonekta ang iyong wallet gamit ang Bluetooth", + "connect_your_hardware_wallet_ble": "Ikonekta ang iyong wallet gamit ang Bluetooth", + "connect_your_hardware_wallet_usb": "Ikonekta ang iyong wallet ng hardware gamit ang USB", "connected": "Konektado", "connection_sync": "Koneksyon at pag-sync", "connectWalletPrompt": "Ikonekta ang iyong wallet sa WalletConnect upang gumawa ng mga transaksyon", @@ -784,6 +786,7 @@ "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.", "select_hw_account_below": "Mangyaring piliin kung aling account ang ibabalik sa ibaba:", + "select_manufacturer_title": "Piliin ang Tagagawa", "select_sell_provider_notice": "Pumili ng provider ng nagbebenta sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na sell provider sa mga setting ng app.", "select_your_country": "Mangyaring piliin ang iyong bansa", "selected_trocador_provider": "Napiling Trocador Provider", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "Maaari kang bumalik sa iyong dapp ngayon", "your": "IYONG", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index fde372e60..e18e00379 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -171,6 +171,7 @@ "collection_description": "Koleksiyon Açıklama", "collection_name": "Toplama adı", "color_theme": "Renk teması", + "coming_soon_tag": "Yakında gelecek", "commit_transaction_amount_fee": "Transferi gerçekleştir\nMiktar: ${amount}\nKomisyon: ${fee}", "confirm": "Onayla", "confirm_delete_template": "Bu eylem, bu şablonu silecek. Devam etmek istiyor musun?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Cihazınızı kek cüzdanında seçin ve kuruluma devam edin.", "connect_yats": "Yat'lara bağlan", "connect_your_hardware_wallet": "Bluetooth veya USB kullanarak donanım cüzdanınızı bağlayın", - "connect_your_hardware_wallet_ios": "Bluetooth kullanarak donanım cüzdanınızı bağlayın", + "connect_your_hardware_wallet_ble": "Bluetooth kullanarak donanım cüzdanınızı bağlayın", + "connect_your_hardware_wallet_usb": "USB kullanarak donanım cüzdanınızı bağlayın", "connected": "Bağlı", "connection_sync": "Bağlantı ve senkronizasyon", "connectWalletPrompt": "İşlem yapmak için cüzdanınızı WalletConnect'e bağlayın", @@ -784,6 +786,7 @@ "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.", "select_hw_account_below": "Lütfen aşağıda hangi hesabı geri yükleyeceğinizi seçin:", + "select_manufacturer_title": "Üretici seçin", "select_sell_provider_notice": "Yukarıdan bir satış sağlayıcısı seçin. Uygulama ayarlarında varsayılan satış sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", "select_your_country": "Lütfen ülkenizi seçin", "selected_trocador_provider": "Seçilmiş Trocador Sağlayıcı", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "Şimdi Dapp'ınıza geri dönebilirsin", "your": "Senin", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c8bc3b4ad..620a036fc 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -171,6 +171,7 @@ "collection_description": "Опис колекції", "collection_name": "Назва колекції", "color_theme": "Кольорова тема", + "coming_soon_tag": "Незабаром", "commit_transaction_amount_fee": "Підтвердити транзакцію \nСума: ${amount}\nКомісія: ${fee}", "confirm": "Підтвердити", "confirm_delete_template": "Ця дія видалить шаблон. Ви хочете продовжити?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Виберіть свій пристрій у гаманці тортів і продовжуйте налаштування.", "connect_yats": "Підключіть Yats", "connect_your_hardware_wallet": "Підключіть апаратний гаманець за допомогою Bluetooth або USB", - "connect_your_hardware_wallet_ios": "Підключіть апаратний гаманець за допомогою Bluetooth", + "connect_your_hardware_wallet_ble": "Підключіть апаратний гаманець за допомогою Bluetooth", + "connect_your_hardware_wallet_usb": "Підключіть свій апаратний гаманець за допомогою USB", "connected": "З'єднаний", "connection_sync": "Підключення та синхронізація", "connectWalletPrompt": "Підключіть свій гаманець до WalletConnect, щоб здійснювати транзакції", @@ -785,6 +787,7 @@ "select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.", "select_destination": "Виберіть місце призначення для файлу резервної копії.", "select_hw_account_below": "Виберіть, який рахунок відновити нижче:", + "select_manufacturer_title": "Виберіть виробника", "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.", "select_your_country": "Будь ласка, виберіть свою країну", "selected_trocador_provider": "Вибраний постачальник Trocador", @@ -1147,4 +1150,4 @@ "youCanGoBackToYourDapp": "Ви можете повернутися до свого DAPP зараз", "your": "Ваш", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index b8cd3d032..f8e1b770a 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -171,6 +171,7 @@ "collection_description": "جمع کرنے کی تفصیل", "collection_name": "جمع کرنے کا نام", "color_theme": "رنگین تھیم", + "coming_soon_tag": "جلد آرہا ہے", "commit_transaction_amount_fee": "لین دین کا ارتکاب کریں\\nرقم: ${amount}\\nفیس: ${fee}", "confirm": "تصدیق کریں۔", "confirm_delete_template": "یہ عمل اس ٹیمپلیٹ کو حذف کر دے گا۔ کیا آپ جاری رکھنا چاہتے ہیں؟", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "اپنے آلے کو کیک پرس میں منتخب کریں اور سیٹ اپ کے ساتھ جاری رکھیں۔", "connect_yats": "Yats کو جوڑیں۔", "connect_your_hardware_wallet": "بلوٹوتھ یا USB کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو مربوط کریں", - "connect_your_hardware_wallet_ios": "بلوٹوتھ کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو جوڑیں", + "connect_your_hardware_wallet_ble": "بلوٹوتھ کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو جوڑیں", + "connect_your_hardware_wallet_usb": "USB کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو مربوط کریں", "connected": "منسلک", "connection_sync": "کنکشن اور مطابقت پذیری", "connectWalletPrompt": "۔ﮟﯾﮌﻮﺟ ﮫﺗﺎﺳ ﮯﮐ WalletConnect ﻮﮐ ﮮﻮﭩﺑ ﮯﻨﭘﺍ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻦﯾﺩ ﻦﯿﻟ", @@ -786,6 +788,7 @@ "select_buy_provider_notice": "اوپر خریدنے والا خریدنے والا منتخب کریں۔ آپ ایپ کی ترتیبات میں اپنے پہلے سے طے شدہ خریدنے والے کو ترتیب دے کر اس اسکرین کو چھوڑ سکتے ہیں۔", "select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ", "select_hw_account_below": "براہ کرم ذیل میں کون سا اکاؤنٹ بحال کرنا ہے منتخب کریں:", + "select_manufacturer_title": "کارخانہ دار کو منتخب کریں", "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ", "select_your_country": "براہ کرم اپنے ملک کو منتخب کریں", "selected_trocador_provider": "منتخب کردہ ٹروکاڈور فراہم کنندہ", @@ -1148,4 +1151,4 @@ "youCanGoBackToYourDapp": "اب آپ اپنے ڈی اے پی پی پر واپس جاسکتے ہیں", "your": "آپ کا", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 6ec1622f5..afd305084 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -169,6 +169,7 @@ "collection_description": "Mô tả bộ sưu tập", "collection_name": "Tên bộ sưu tập", "color_theme": "Chủ đề màu sắc", + "coming_soon_tag": "Sắp ra mắt", "commit_transaction_amount_fee": "Cam kết giao dịch\nSố tiền: ${amount}\nPhí: ${fee}", "confirm": "Xác nhận", "confirm_delete_template": "Thao tác này sẽ xóa mẫu này. Bạn có muốn tiếp tục không?", @@ -191,7 +192,8 @@ "connect_hw_info_step_4": "Chọn thiết bị của bạn trong ví bánh và tiếp tục với thiết lập.", "connect_yats": "Kết nối Yats", "connect_your_hardware_wallet": "Kết nối ví phần cứng của bạn bằng Bluetooth hoặc USB", - "connect_your_hardware_wallet_ios": "Kết nối ví phần cứng của bạn bằng Bluetooth", + "connect_your_hardware_wallet_ble": "Kết nối ví phần cứng của bạn bằng Bluetooth", + "connect_your_hardware_wallet_usb": "Kết nối ví phần cứng của bạn bằng USB", "connected": "Kết nối", "connection_sync": "Kết nối và đồng bộ hóa", "connectWalletPrompt": "Kết nối ví của bạn với WalletConnect để thực hiện giao dịch", @@ -782,6 +784,7 @@ "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.", "select_hw_account_below": "Vui lòng chọn tài khoản nào để khôi phục bên dưới:", + "select_manufacturer_title": "Chọn nhà sản xuất", "select_sell_provider_notice": "Chọn nhà cung cấp bán ở 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 bán mặc định trong cài đặt ứng dụng.", "select_your_country": "Vui lòng chọn quốc gia của bạn", "sell": "Bán", @@ -1143,4 +1146,4 @@ "youCanGoBackToYourDapp": "Bạn có thể quay lại DAPP của mình ngay bây giờ", "your": "Của bạn", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 4c84780e2..9c589871e 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -171,6 +171,7 @@ "collection_description": "Ijuwe akopọ", "collection_name": "Orukọ ikojọpọ", "color_theme": "Àwọn ààtò àwọ̀", + "coming_soon_tag": "Nbọ laipẹ", "commit_transaction_amount_fee": "Jẹ́rìí sí àránṣẹ́\nOwó: ${amount}\nIye àfikún: ${fee}", "confirm": "Jẹ́rìísí", "confirm_delete_template": "Ìṣe yìí máa yọ àwòṣe yìí kúrò. Ṣé ẹ fẹ́ tẹ̀síwájú?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "Yan ẹrọ rẹ ni apamọwọ akara oyinbo ki o tẹsiwaju pẹlu iṣeto.", "connect_yats": "So àwọn Yat", "connect_your_hardware_wallet": "So apamọwọ irinṣẹ rẹ nipa lilo Bluetooth tabi USB", - "connect_your_hardware_wallet_ios": "So apamọwọ ẹrọ rẹ ni lilo Bluetooth", + "connect_your_hardware_wallet_ble": "So apamọwọ ẹrọ rẹ ni lilo Bluetooth", + "connect_your_hardware_wallet_usb": "So apamọwọ ohun elo rẹ ni lilo USB", "connected": "Sopọ", "connection_sync": "Ìkànpọ̀ àti ìbádọ́gba", "connectWalletPrompt": "So apamọwọ rẹ pọ pẹlu WalletConnect lati ṣe awọn iṣowo", @@ -785,6 +787,7 @@ "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.", "select_hw_account_below": "Jọwọ yan iru iroyin lati mu pada ni isalẹ:", + "select_manufacturer_title": "Yan olupese", "select_sell_provider_notice": "Yan olupese ti o ta loke. O le foju iboju yii nipa tito olupese iṣẹ tita aiyipada rẹ ni awọn eto app.", "select_your_country": "Jọwọ yan orilẹ-ede rẹ", "selected_trocador_provider": "olupese trocador ti a yan", @@ -1147,4 +1150,4 @@ "youCanGoBackToYourDapp": "O le pada si tapla rẹ bayi", "your": "Rẹ", "yy": "Ọd" -} \ No newline at end of file +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 86d65ed2f..fdfa4d438 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -171,6 +171,7 @@ "collection_description": "集合描述", "collection_name": "收集名称", "color_theme": "主题", + "coming_soon_tag": "即将推出", "commit_transaction_amount_fee": "提交交易\n金额: ${amount}\n手续费: ${fee}", "confirm": "确认", "confirm_delete_template": "此操作将刪除此模板。确定吗?", @@ -192,7 +193,8 @@ "connect_hw_info_step_4": "在蛋糕钱包中选择您的设备,然后继续设置。", "connect_yats": "连接 Yats", "connect_your_hardware_wallet": "使用蓝牙或USB连接硬件钱包", - "connect_your_hardware_wallet_ios": "使用蓝牙连接硬件钱包", + "connect_your_hardware_wallet_ble": "使用蓝牙连接硬件钱包", + "connect_your_hardware_wallet_usb": "使用USB连接硬件钱包", "connected": "连接", "connection_sync": "连接和同步", "connectWalletPrompt": "将您的钱包与 WalletConnect 连接以进行交易", @@ -784,6 +786,7 @@ "select_buy_provider_notice": "在上面选择买入提供商。您可以通过在应用程序设置中设置默认的购买提供商来跳过此屏幕。", "select_destination": "请选择备份文件的目的地。", "select_hw_account_below": "请在下面选择要还原的帐户:", + "select_manufacturer_title": "选择制造商", "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。", "select_your_country": "请选择你的国家", "selected_trocador_provider": "选定的trocador提供商", @@ -1146,4 +1149,4 @@ "youCanGoBackToYourDapp": "您现在可以回到DAPP", "your": "你的", "yy": "YY" -} \ No newline at end of file +} diff --git a/scripts/build_bitbox_flutter.sh b/scripts/build_bitbox_flutter.sh new file mode 100755 index 000000000..8decc73d2 --- /dev/null +++ b/scripts/build_bitbox_flutter.sh @@ -0,0 +1,8 @@ +git clone https://github.com/konstantinullrich/bitbox_flutter +cd bitbox_flutter +./build_bindings.sh --dont-install + +FILE=api.aar +if [ -f "$FILE" ]; then + echo "$FILE exists." +fi diff --git a/tool/configure.dart b/tool/configure.dart index c41ccc496..347eb79a2 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -91,6 +91,7 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/output_info.dart'; @@ -110,6 +111,7 @@ import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bip39/bip39.dart' as bip39; """; @@ -133,8 +135,9 @@ import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart'; -import 'package:cw_bitcoin/bitcoin_hardware_wallet_service.dart'; -import 'package:cw_bitcoin/litecoin_hardware_wallet_service.dart'; +import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart'; +import 'package:cw_bitcoin/hardware/litecoin_ledger_service.dart'; +import 'package:cw_bitcoin/hardware/bitbox_service.dart'; import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; @@ -168,7 +171,7 @@ abstract class Bitcoin { String? passphrase, }); WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo}); - WalletCredentials createBitcoinWalletFromKeys({required String name, required String password, required String xpub}); + WalletCredentials createBitcoinWalletFromKeys({required String name, required String password, required String xpub, HardwareWalletType? hardwareWalletType}); WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo}); List getWordList(); @@ -213,7 +216,7 @@ abstract class Bitcoin { Future setAddressType(Object wallet, dynamic option); ReceivePageOption getSelectedAddressType(Object wallet); List getBitcoinReceivePageOptions(Object wallet); - List getLitecoinReceivePageOptions(); + List getLitecoinReceivePageOptions(Object wallet); BitcoinAddressType getBitcoinAddressType(ReceivePageOption option); bool isPayjoinAvailable(Object wallet); bool hasSelectedSilentPayments(Object wallet); @@ -243,8 +246,9 @@ abstract class Bitcoin { Future updateFeeRates(Object wallet); int getMaxCustomFeeRate(Object wallet); void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); - Future> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); - Future> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service); + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection, bool isBitcoin); + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager, bool isBitcoin); List updateOutputs(PendingTransaction pendingTransaction, List outputs); bool txIsReceivedSilentPayment(TransactionInfo txInfo); bool txIsMweb(TransactionInfo txInfo); @@ -676,11 +680,11 @@ abstract class WowneroAccountList { Future generateEthereum(bool hasImplementation) async { final outputFile = File(ethereumOutputPath); const ethereumCommonHeaders = """ -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -692,6 +696,7 @@ import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox; import 'package:web3dart/web3dart.dart'; """; @@ -702,9 +707,11 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; -import 'package:cw_evm/evm_chain_hardware_wallet_service.dart'; -import 'package:cw_evm/evm_ledger_credentials.dart'; +import 'package:cw_evm/hardware/evm_ledger_credentials.dart'; +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart'; import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart'; +import 'package:cw_evm/hardware/evm_chain_ledger_service.dart'; import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_wallet.dart'; @@ -770,8 +777,9 @@ abstract class Ethereum { Future reinvestDEuroInterest(WalletBase wallet, TransactionPriority priority); Future enableDEuroSaving(WalletBase wallet, TransactionPriority priority); - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); - Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service); + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection); + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager); List getDefaultTokenContractAddresses(); bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress); } @@ -797,11 +805,11 @@ abstract class Ethereum { Future generatePolygon(bool hasImplementation) async { final outputFile = File(polygonOutputPath); const polygonCommonHeaders = """ -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -813,6 +821,7 @@ import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox; import 'package:web3dart/web3dart.dart'; """; @@ -823,9 +832,11 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; -import 'package:cw_evm/evm_chain_hardware_wallet_service.dart'; -import 'package:cw_evm/evm_ledger_credentials.dart'; +import 'package:cw_evm/hardware/evm_ledger_credentials.dart'; +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart'; import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart'; +import 'package:cw_evm/hardware/evm_chain_ledger_service.dart'; import 'package:cw_polygon/polygon_client.dart'; import 'package:cw_polygon/polygon_wallet.dart'; @@ -881,8 +892,9 @@ abstract class Polygon { Web3Client? getWeb3Client(WalletBase wallet); String getTokenAddress(CryptoCurrency asset); - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); - Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service); + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection); + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager); List getDefaultTokenContractAddresses(); bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress); }