From e04185a7c1c96f2bd155a5a491d21b80a0f9b24f Mon Sep 17 00:00:00 2001 From: OmarHatem <omarh.ismail1@gmail.com> Date: Tue, 22 Oct 2024 21:06:08 +0300 Subject: [PATCH 1/2] upload x86 arch to artifacts [skip ci] --- .github/workflows/pr_test_build_android.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 925f4e00d..c9021fac0 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -224,6 +224,7 @@ jobs: cd /opt/android/cake_wallet/build/app/outputs/flutter-apk mkdir test-apk cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk + cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk - name: Upload Artifact uses: kittaakos/upload-artifact-as-is@v0 From 68926c0a337700165b807e76f34a31c52a9d0b79 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich <konstantinullrich12@gmail.com> Date: Wed, 23 Oct 2024 17:38:31 +0200 Subject: [PATCH 2/2] Cw 679 add ledger litecoin support (#1565) * Add Litecoin Hardware Wallet Creation * Add Litecoin Hardware Wallet Creation * Fix Bitcoin not sending on Ledger * Fixes to sending LTC using Ledger * CW-679 Fix merge conflicts * CW-679 Fix merge conflicts * CW-679 Minor fixes * CW-679 Add derivation Path of change address * ledger flutter plus refactoring * ledger flutter plus refactoring * ledger flutter plus refactoring * Ups :| * Ups :| I forgot USB * Handle BT Off * Fix Issue with A14 and USB * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Pls work * Pls work * Pls work * Pls work * Fix overpopulation * Fix ble device detection and support for Stax and Flex * clean up pubspec * clean up * MWeb merge fix * MWeb merge fix * Fix Merge conflicts * Fix Requested changes --- assets/images/hardware_wallet/ledger_flex.png | Bin 0 -> 1777 bytes .../images/hardware_wallet/ledger_nano_s.png | Bin 0 -> 2639 bytes .../images/hardware_wallet/ledger_nano_x.png | Bin 0 -> 2634 bytes assets/images/hardware_wallet/ledger_stax.png | Bin 0 -> 1612 bytes assets/images/ledger_nano.png | Bin 1463 -> 0 bytes .../lib/bitcoin_hardware_wallet_service.dart | 21 +-- cw_bitcoin/lib/bitcoin_wallet.dart | 57 ++++---- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 1 + cw_bitcoin/lib/electrum_wallet.dart | 69 ++++++--- cw_bitcoin/lib/electrum_wallet_addresses.dart | 21 ++- .../lib/litecoin_hardware_wallet_service.dart | 46 ++++++ cw_bitcoin/lib/litecoin_wallet.dart | 102 +++++++++++-- cw_bitcoin/lib/litecoin_wallet_addresses.dart | 29 ++-- cw_bitcoin/lib/litecoin_wallet_service.dart | 23 ++- cw_bitcoin/lib/psbt_transaction_builder.dart | 4 - cw_bitcoin/pubspec.lock | 134 +++++++++++------- cw_bitcoin/pubspec.yaml | 19 +-- .../lib/src/bitcoin_cash_wallet.dart | 1 + .../src/bitcoin_cash_wallet_addresses.dart | 1 + .../lib/hardware/device_connection_type.dart | 1 + .../evm_chain_hardware_wallet_service.dart | 18 +-- .../evm_chain_transaction_credentials.dart | 1 - cw_evm/lib/evm_ledger_credentials.dart | 57 ++++---- cw_evm/pubspec.yaml | 9 +- lib/bitcoin/cw_bitcoin.dart | 24 +++- .../hardware_wallet_device.dart | 65 +++++++++ lib/ethereum/cw_ethereum.dart | 16 +-- lib/main.dart | 14 +- lib/polygon/cw_polygon.dart | 15 +- .../connect_device/connect_device_page.dart | 120 ++++++++++------ .../connect_device/debug_device_page.dart | 112 +++++++++------ .../connect_device/widgets/device_tile.dart | 2 +- .../screens/dashboard/pages/balance_page.dart | 2 +- .../screens/restore/restore_options_page.dart | 4 +- lib/src/screens/send/send_page.dart | 11 +- .../dashboard/dashboard_view_model.dart | 2 +- .../hardware_wallet/ledger_view_model.dart | 109 ++++++++++---- lib/view_model/send/send_view_model.dart | 19 ++- .../wallet_hardware_restore_view_model.dart | 13 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec_base.yaml | 9 +- res/values/strings_de.arb | 74 +++++----- tool/configure.dart | 16 ++- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 45 files changed, 842 insertions(+), 405 deletions(-) create mode 100644 assets/images/hardware_wallet/ledger_flex.png create mode 100644 assets/images/hardware_wallet/ledger_nano_s.png create mode 100644 assets/images/hardware_wallet/ledger_nano_x.png create mode 100644 assets/images/hardware_wallet/ledger_stax.png delete mode 100644 assets/images/ledger_nano.png create mode 100644 cw_bitcoin/lib/litecoin_hardware_wallet_service.dart create mode 100644 lib/entities/hardware_wallet/hardware_wallet_device.dart diff --git a/assets/images/hardware_wallet/ledger_flex.png b/assets/images/hardware_wallet/ledger_flex.png new file mode 100644 index 0000000000000000000000000000000000000000..fa39f241fccf109565a28f66c090bf57ca274fd0 GIT binary patch literal 1777 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 ToY=F<00000NkvXXu0mjfR%KGE literal 0 HcmV?d00001 diff --git a/assets/images/hardware_wallet/ledger_nano_s.png b/assets/images/hardware_wallet/ledger_nano_s.png new file mode 100644 index 0000000000000000000000000000000000000000..02777aeb69b2e436d2b9bd96c3b5f39a773b7def GIT binary patch literal 2639 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 literal 0 HcmV?d00001 diff --git a/assets/images/hardware_wallet/ledger_nano_x.png b/assets/images/hardware_wallet/ledger_nano_x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9328a5ef787f64501b3fd393932fb9f393d0468 GIT binary patch literal 2634 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 literal 0 HcmV?d00001 diff --git a/assets/images/hardware_wallet/ledger_stax.png b/assets/images/hardware_wallet/ledger_stax.png new file mode 100644 index 0000000000000000000000000000000000000000..06c9c848e8336bacbe4378b5e5999604393821e6 GIT binary patch literal 1612 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 KMNUMnLSTZg0^5E7 literal 0 HcmV?d00001 diff --git a/assets/images/ledger_nano.png b/assets/images/ledger_nano.png deleted file mode 100644 index bb61ba1750728b5584680110bd90727c3e195eca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1463 zcmV;o1xWgdP)<h;3K|Lk000e1NJLTq003YB003YJ1^@s6;+S_h00009a7bBm001mY z001mY0i`{bsQ>@~0drDELIAGL9O(c600d`2O+f$vv5yP<VFdsH1x!gqK~#7F?VP=H z8$}er-=034bZ#-sAj)87q6`+K!(cK46`BD%bSQs8oQBwyr2<4~l5})29oR4<ItntD zJ7c?06NU~^kfB4Vu&v}?-paZeN73Exee6lTeZR`KR_D9-yW5ZV?hb%q7=~dOhG7_n zNfiMTV&SvqqHx3~fKVRpNvD5;_dBiiE?C4w1T3uF*${yBai0kg_5H2e4X}h9q)bro zhb+D}e+~Y5^xH02K&k>Xg70CCgwzCt2)>6E5>gQmA^0Admtb5#jNp4@T7ppl2?XCG zvl5I6&<#GdX&}HZO+ObEO*1LMh=4G`-``rVKYsMEJvYB3Y?P3=fUv>$ez#RZq5`4@ z-@`@;i3x}sd=J|sBqAWG;Ct94A+~^|gYRLBgjfPh1>eI436TYu4!%d$B}5WX{=8Yr zA+%KO;s)O%%Mv09c=gh?Iz{7ZP@ANJ?~zpr4n%;;XqSUOPd4}-hqO(kZL8W*oBBJ{ zMZ!5Ck^qg(A{ZiT5+Zd$ugrgVMbQNr?FO>c9Yq@h-ds8oLevz^Sb_$T1U&xZVRsr1 zJ_`~euLGv#;Lp2+SSw+c5TDq&AeIo1M6#hQAv%fG16V>d60L}_gzzL@6J`nFFmhJ} zOVG!N00~Rb!<YaGOHgA}fP^IkV_blQCHRwy00~PNPig`rVGe%o($cQXx<ZEcLt>~R z=8tYgi|P$ufI;;41FB$<)CEZN4j!Mt6zm5I=z&2dB0!=#cvm=Wn1cNP7usNuRL6i( z{Q6>fSH<Dh+INr=LHlvAy}k(s5ikV|o|+%_wTRF<{As-n6X5MnzpgsYsf0=H2g5K7 zV+WxI;c~S;o1eK@6E0o`i$H;YgK&EA%<UvSFl!5_T>thapnDIPE+Dk~eQYH0ZX+!L z2|n?jEf6;2ickuW4`UQu@fR>Yh@K;$@%xXrBlQl-R74&fqsgfA56-%Xccb14uM{BA zEd!$@>hlN_OU7jpDdFAgcUHrECl4{80i;VglQv`=1SnB`qAa@`Ra+Nfqg-vabO+{G z0%Y(Hw%2ZeB~+H1H4?EQh4{C^*+M})pda1`4Y&&mflN)GoE%-Dv;2NwLjj92!wVY3 zx&pGs*_Hla^-IN=(UotfRhJdA)yhhfx+DLx=`g+CR{c-D0*1)NW}$fDKS5JUI)iIe zoY@eL*r_akvqOD)X|#R+<akw%fxa6Qj#f>Z1%P6uPu)qOD0bx4arV#hN<a*%C*y2o z`77lV-g*IYrLU1@DHV$|FHJA|{o|Hg3HyEN88|FJh3-lpQM@<$x8YUL@%s*?C`zEf zdI8!ieV<?oIavE{Tf7bmtQX*-(7f+Qw_P~+3P4o8c4vT3SAzj(IR;!E9@Yzx^L%wW zMf7t0@iU&&6ouc{9OYHR(@-Lx!yEo{dg^-6y|p(cw<A*%f%zdSVS?&^0|R?M41)WH z3@8e?NsF-4pd$W4Xa2o$!5^#(C@<u<O)2z1qkhZ+&M>?UaZ{&lp?Hykk0rm)b+g1Q zASCk4FU(xHOA)RGweuS@MqvS(3<6)6sSdgwR4lNlSihG>JwWD3C|UIYvQG&$3o@rO zCRgk1m&CmSN-}c@79c?HHFz|LH}*o@roz+E1Q|Tn5c2jEr2rI;axTk6e^sYaz|-UZ zH-QO2^k{sz74<!-%14y`{j+^E_tpnbXmRopFfSnI`uRS7y#MpsuMiRKn{yXdnsr&G z1M3;2CcaPV5vEYLr(dP&(Nij5*m!%oi_pzMu5+;cC69>=!!QiPFbu=Q;yG(reAP{& RfT{oh002ovPDHLkV1g$1iR}OY diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart index de339175d..a02c51c69 100644 --- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart @@ -5,30 +5,31 @@ 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/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; class BitcoinHardwareWalletService { - BitcoinHardwareWalletService(this.ledger, this.device); + BitcoinHardwareWalletService(this.ledgerConnection); - final Ledger ledger; - final LedgerDevice device; + final LedgerConnection ledgerConnection; - Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async { - final bitcoinLedgerApp = BitcoinLedgerApp(ledger); + Future<List<HardwareAccountData>> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection); - final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device); - print(masterFp); + final masterFp = await bitcoinLedgerApp.getMasterFingerprint(); final accounts = <HardwareAccountData>[]; final indexRange = List.generate(limit, (i) => i + index); for (final i in indexRange) { final derivationPath = "m/84'/0'/$i'"; - final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath); + 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); + final address = generateP2WPKHAddress( + hd: hd, index: 0, network: BitcoinNetwork.mainnet); accounts.add(HardwareAccountData( address: address, diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 30f04667a..908897845 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -5,13 +5,13 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_bitcoin/psbt_transaction_builder.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/psbt_transaction_builder.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; @@ -19,7 +19,7 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; part 'bitcoin_wallet.g.dart'; @@ -61,8 +61,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialBalance: initialBalance, seedBytes: seedBytes, encryptionFileUtils: encryptionFileUtils, - currency: - networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, + currency: networkParam == BitcoinNetwork.testnet + ? CryptoCurrency.tbtc + : CryptoCurrency.btc, alwaysScan: alwaysScan, ) { // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) @@ -80,11 +81,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { mainHd: hd, sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: networkParam ?? network, - masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, + masterHd: + seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, + isHardwareWallet: walletInfo.isHardwareWallet, ); autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + this.walletAddresses.isEnabledAutoGenerateSubaddress = + this.isEnabledAutoGenerateSubaddress; }); } @@ -185,8 +189,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { walletInfo.derivationInfo ??= DerivationInfo(); // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; + walletInfo.derivationInfo!.derivationPath ??= + snp?.derivationPath ?? electrum_path; + walletInfo.derivationInfo!.derivationType ??= + snp?.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; final mnemonic = keysData.mnemonic; @@ -228,15 +234,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { ); } - Ledger? _ledger; - LedgerDevice? _ledgerDevice; + LedgerConnection? _ledgerConnection; BitcoinLedgerApp? _bitcoinLedgerApp; - void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { - _ledger = setLedger; - _ledgerDevice = setLedgerDevice; - _bitcoinLedgerApp = - BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); + @override + void setLedgerConnection(LedgerConnection connection) { + _ledgerConnection = connection; + _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!, + derivationPath: walletInfo.derivationInfo!.derivationPath!); } @override @@ -251,12 +256,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, }) async { - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!); + final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[]; for (final utxo in utxos) { - final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); - final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; + final rawTx = + await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + final publicKeyAndDerivationPath = + publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; psbtReadyInputs.add(PSBTReadyUtxoWithAddress( utxo: utxo.utxo, @@ -268,10 +275,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { )); } - final psbt = - PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); + final psbt = PSBTTransactionBuild( + inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); - final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); + final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt); return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); } @@ -279,14 +286,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Future<String> signMessage(String message, {String? address = null}) async { if (walletInfo.isHardwareWallet) { final addressEntry = address != null - ? walletAddresses.allAddresses.firstWhere((element) => element.address == address) + ? walletAddresses.allAddresses + .firstWhere((element) => element.address == address) : null; final index = addressEntry?.index ?? 0; final isChange = addressEntry?.isHidden == true ? 1 : 0; final accountPath = walletInfo.derivationInfo?.derivationPath; - final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; + final derivationPath = + accountPath != null ? "$accountPath/$isChange/$index" : null; - final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!, + final signature = await _bitcoinLedgerApp!.signMessage( message: ascii.encode(message), signDerivationPath: derivationPath); return base64Encode(signature); } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 697719894..04a3cae36 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -15,6 +15,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S required super.mainHd, required super.sideHd, required super.network, + required super.isHardwareWallet, super.initialAddresses, super.initialRegularAddressIndex, super.initialChangeAddressIndex, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 4d127c248..4b80cb201 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -6,7 +6,6 @@ import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:cw_core/encryption_file_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:collection/collection.dart'; import 'package:cw_bitcoin/address_from_output.dart'; @@ -26,6 +25,8 @@ import 'package:cw_bitcoin/exceptions.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -37,10 +38,10 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; @@ -51,9 +52,10 @@ part 'electrum_wallet.g.dart'; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; -abstract class ElectrumWalletBase - extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo> - with Store, WalletKeysFile { +abstract class ElectrumWalletBase extends WalletBase< + ElectrumBalance, + ElectrumTransactionHistory, + ElectrumTransactionInfo> with Store, WalletKeysFile { ElectrumWalletBase({ required String password, required WalletInfo walletInfo, @@ -69,8 +71,8 @@ abstract class ElectrumWalletBase ElectrumBalance? initialBalance, CryptoCurrency? currency, this.alwaysScan, - }) : accountHD = - getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), + }) : accountHD = getAccountHDWallet( + currency, network, seedBytes, xpub, walletInfo.derivationInfo), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = <int>[], @@ -105,8 +107,12 @@ 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) { if (seedBytes == null && xpub == null) { throw Exception( "To create a Wallet you need either a seed or an xpub. This should not happen"); @@ -117,8 +123,9 @@ abstract class ElectrumWalletBase case CryptoCurrency.btc: case CryptoCurrency.ltc: case CryptoCurrency.tbtc: - return Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( - _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) + return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)) + .derivePath(_hardenedDerivationPath( + derivationInfo?.derivationPath ?? electrum_path)) as Bip32Slip10Secp256k1; case CryptoCurrency.bch: return bitcoinCashHDWallet(seedBytes); @@ -127,15 +134,26 @@ abstract class ElectrumWalletBase } } - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!); + return Bip32Slip10Secp256k1.fromExtendedKey( + xpub!, getKeyNetVersion(network)); } static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => - Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1; + Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") + as Bip32Slip10Secp256k1; static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; + static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) { + switch (network) { + case LitecoinNetwork.mainnet: + return Bip44Conf.litecoinMainNet.altKeyNetVer; + default: + return null; + } + } + bool? alwaysScan; final Bip32Slip10Secp256k1 accountHD; @@ -634,8 +652,9 @@ abstract class ElectrumWalletBase ECPrivate? privkey; bool? isSilentPayment = false; - final hd = - utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; + final hd = utx.bitcoinAddressRecord.isHidden + ? walletAddresses.sideHd + : walletAddresses.mainHd; if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; @@ -833,7 +852,7 @@ abstract class ElectrumWalletBase inputs: utxoDetails.availableInputs, outputs: updatedOutputs, ); - final address = RegexUtils.addressTypeFromStr(changeAddress, network); + final address = RegexUtils.addressTypeFromStr(changeAddress.address, network); updatedOutputs.add(BitcoinOutput( address: address, value: BigInt.from(amountLeftForChangeAndFee), @@ -845,6 +864,14 @@ abstract class ElectrumWalletBase isChange: true, )); + // Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets + final changeDerivationPath = + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "/${changeAddress.isHidden ? "1" : "0"}" + "/${changeAddress.index}"; + utxoDetails.publicKeys[address.pubKeyHash()] = + PublicKeyWithDerivationPath('', changeDerivationPath); + // calcFee updates the silent payment outputs to calculate the tx size accounting // for taproot addresses, but if more inputs are needed to make up for fees, // the silent payment outputs need to be recalculated for the new inputs @@ -1206,6 +1233,9 @@ abstract class ElectrumWalletBase } } + void setLedgerConnection(ledger.LedgerConnection connection) => + throw UnimplementedError(); + Future<BtcTransaction> buildHardwareWalletTransaction({ required List<BitcoinBaseOutput> outputs, required BigInt fee, @@ -1563,7 +1593,9 @@ abstract class ElectrumWalletBase final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network); final privkey = generateECPrivate( - hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + hd: addressRecord.isHidden + ? walletAddresses.sideHd + : walletAddresses.mainHd, index: addressRecord.index, network: network); @@ -1745,7 +1777,8 @@ abstract class ElectrumWalletBase if (height != null) { if (time == null && height > 0) { - time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round(); + time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000) + .round(); } if (confirmations == null) { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 6c10dc615..2686d12cc 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -36,6 +36,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { required this.mainHd, required this.sideHd, required this.network, + required this.isHardwareWallet, List<BitcoinAddressRecord>? initialAddresses, Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialChangeAddressIndex, @@ -44,6 +45,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { List<BitcoinAddressRecord>? initialMwebAddresses, Bip32Slip10Secp256k1? masterHd, BitcoinAddressType? initialAddressPageType, + }) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()), addressesByReceiveType = ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()), @@ -112,6 +114,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final BasedUtxoNetwork network; final Bip32Slip10Secp256k1 mainHd; final Bip32Slip10Secp256k1 sideHd; + final bool isHardwareWallet; @observable SilentPaymentOwner? silentAddress; @@ -240,15 +243,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); } else if (walletInfo.type == WalletType.litecoin) { await _generateInitialAddresses(type: SegwitAddresType.p2wpkh); - if (Platform.isAndroid || Platform.isIOS) { + if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { await _generateInitialAddresses(type: SegwitAddresType.mweb); } } else if (walletInfo.type == WalletType.bitcoin) { await _generateInitialAddresses(); - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); - await _generateInitialAddresses(type: SegwitAddresType.p2tr); - await _generateInitialAddresses(type: SegwitAddresType.p2wsh); + if (!isHardwareWallet) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); + await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); + await _generateInitialAddresses(type: SegwitAddresType.p2tr); + await _generateInitialAddresses(type: SegwitAddresType.p2wsh); + } } updateAddressesByMatch(); @@ -267,7 +272,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } @action - Future<String> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async { + Future<BitcoinAddressRecord> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async { updateChangeAddresses(); if (changeAddresses.isEmpty) { @@ -282,7 +287,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } updateChangeAddresses(); - final address = changeAddresses[currentChangeAddressIndex].address; + final address = changeAddresses[currentChangeAddressIndex]; currentChangeAddressIndex += 1; return address; } @@ -670,7 +675,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd; + bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; + bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => !addr.isHidden && !addr.isUsed && addr.type == type; diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart new file mode 100644 index 000000000..62840933c --- /dev/null +++ b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart @@ -0,0 +1,46 @@ +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_flutter_plus/ledger_flutter_plus.dart'; +import 'package:ledger_litecoin/ledger_litecoin.dart'; + +class LitecoinHardwareWalletService { + LitecoinHardwareWalletService(this.ledgerConnection); + + final LedgerConnection ledgerConnection; + + Future<List<HardwareAccountData>> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection); + + await litecoinLedgerApp.getVersion(); + + final accounts = <HardwareAccountData>[]; + final indexRange = List.generate(limit, (i) => i + index); + final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer; + + for (final i in indexRange) { + final derivationPath = "m/84'/2'/$i'"; + 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 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/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index afa8cc388..1fb39c878 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,5 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; + import 'package:convert/convert.dart' as convert; import 'dart:math'; import 'package:collection/collection.dart'; @@ -37,6 +39,8 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:grpc/grpc.dart'; import 'package:hive/hive.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; +import 'package:ledger_litecoin/ledger_litecoin.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_mweb/cw_mweb.dart'; @@ -50,12 +54,13 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; abstract class LitecoinWalletBase extends ElectrumWallet with Store { LitecoinWalletBase({ - required String mnemonic, required String password, required WalletInfo walletInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo, - required Uint8List seedBytes, required EncryptionFileUtils encryptionFileUtils, + Uint8List? seedBytes, + String? mnemonic, + String? xpub, String? passphrase, String? addressPageType, List<BitcoinAddressRecord>? initialAddresses, @@ -68,6 +73,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { }) : super( mnemonic: mnemonic, password: password, + xpub: xpub, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, network: LitecoinNetwork.mainnet, @@ -78,8 +84,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { currency: CryptoCurrency.ltc, alwaysScan: alwaysScan, ) { - mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1; - mwebEnabled = alwaysScan ?? false; + if (seedBytes != null) { + mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( + "m/1000'") as Bip32Slip10Secp256k1; + mwebEnabled = alwaysScan ?? false; + } else { + mwebHd = null; + mwebEnabled = false; + } walletAddresses = LitecoinWalletAddresses( walletInfo, initialAddresses: initialAddresses, @@ -91,6 +103,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { network: network, mwebHd: mwebHd, mwebEnabled: mwebEnabled, + isHardwareWallet: walletInfo.isHardwareWallet, ); autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; @@ -126,7 +139,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } }); } - late final Bip32Slip10Secp256k1 mwebHd; + late final Bip32Slip10Secp256k1? mwebHd; late final Box<MwebUtxo> mwebUtxosBox; Timer? _syncTimer; Timer? _feeRatesTimer; @@ -138,8 +151,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @observable SyncStatus mwebSyncStatus = NotConnectedSyncStatus(); - List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; - List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw; + List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; + List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw; static Future<LitecoinWallet> create( {required String mnemonic, @@ -249,14 +262,15 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } return LitecoinWallet( - mnemonic: keysData.mnemonic!, + mnemonic: keysData.mnemonic, + xpub: keysData.xPub, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp?.addresses, initialMwebAddresses: snp?.mwebAddresses, initialBalance: snp?.balance, - seedBytes: seedBytes!, + seedBytes: seedBytes, passphrase: passphrase, encryptionFileUtils: encryptionFileUtils, initialRegularAddressIndex: snp?.regularAddressIndex, @@ -935,7 +949,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { if (!mwebEnabled) { tx.changeAddressOverride = - await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false); + (await (walletAddresses as LitecoinWalletAddresses) + .getChangeAddress(isPegIn: false)) + .address; return tx; } await waitForMwebAddresses(); @@ -974,8 +990,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { bool isPegIn = !hasMwebInput && hasMwebOutput; bool isRegular = !hasMwebInput && !hasMwebOutput; - tx.changeAddressOverride = await (walletAddresses as LitecoinWalletAddresses) - .getChangeAddress(isPegIn: isPegIn || isRegular); + tx.changeAddressOverride = + (await (walletAddresses as LitecoinWalletAddresses) + .getChangeAddress(isPegIn: isPegIn || isRegular)) + .address; if (!hasMwebInput && !hasMwebOutput) { tx.isMweb = false; return tx; @@ -1215,4 +1233,64 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { return false; } + + LedgerConnection? _ledgerConnection; + LitecoinLedgerApp? _litecoinLedgerApp; + + @override + void setLedgerConnection(LedgerConnection connection) { + _ledgerConnection = connection; + _litecoinLedgerApp = + LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!); + } + + @override + Future<BtcTransaction> buildHardwareWalletTransaction({ + required List<BitcoinBaseOutput> outputs, + required BigInt fee, + required BasedUtxoNetwork network, + required List<UtxoWithAddress> utxos, + required Map<String, PublicKeyWithDerivationPath> publicKeys, + String? memo, + bool enableRBF = false, + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, + }) async { + final readyInputs = <LedgerTransaction>[]; + for (final utxo in utxos) { + final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; + + readyInputs.add(LedgerTransaction( + rawTx: rawTx, + outputIndex: utxo.utxo.vout, + ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)), + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, + // sequence: enableRBF ? 0x1 : 0xffffffff, + sequence: 0xffffffff, + )); + } + + String? changePath; + for (final output in outputs) { + final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; + if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; + } + + + final rawHex = await _litecoinLedgerApp!.createTransaction( + inputs: readyInputs, + outputs: outputs + .map((e) => TransactionOutput.fromBigInt( + (e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) + .toList(), + changePath: changePath, + sigHashType: 0x01, + additionals: ["bech32"], + isSegWit: true, + useTrustedInputForSegwit: true + ); + + return BtcTransaction.fromRaw(rawHex); + } } diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index 6154a0ead..c55f5fc76 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -16,14 +16,17 @@ import 'package:mobx/mobx.dart'; part 'litecoin_wallet_addresses.g.dart'; -class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses; +class LitecoinWalletAddresses = LitecoinWalletAddressesBase + with _$LitecoinWalletAddresses; -abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store { +abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses + with Store { LitecoinWalletAddressesBase( WalletInfo walletInfo, { required super.mainHd, required super.sideHd, required super.network, + required super.isHardwareWallet, required this.mwebHd, required this.mwebEnabled, super.initialAddresses, @@ -37,20 +40,20 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with print("initialized with ${mwebAddrs.length} mweb addresses"); } - final Bip32Slip10Secp256k1 mwebHd; + final Bip32Slip10Secp256k1? mwebHd; bool mwebEnabled; int mwebTopUpIndex = 1000; List<String> mwebAddrs = []; bool generating = false; - List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; - + List<int> get scanSecret => + mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; List<int> get spendPubkey => - mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed; + mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed; @override Future<void> init() async { - await initMwebAddresses(); + if (!isHardwareWallet) await initMwebAddresses(); await super.init(); } @@ -94,6 +97,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with print("Done generating MWEB addresses len: ${mwebAddrs.length}"); // ensure mweb addresses are up to date: + // This is the Case if the Litecoin Wallet is a hardware Wallet + if (mwebHd == null) return; + if (mwebAddresses.length < mwebAddrs.length) { List<BitcoinAddressRecord> addressRecords = mwebAddrs .asMap() @@ -143,7 +149,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with @action @override - Future<String> getChangeAddress( + Future<BitcoinAddressRecord> getChangeAddress( {List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async { // use regular change address on peg in, otherwise use mweb for change address: @@ -187,7 +193,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with if (mwebEnabled) { await ensureMwebAddressUpToIndexExists(1); - return mwebAddrs[0]; + return BitcoinAddressRecord( + mwebAddrs[0], + index: 0, + type: SegwitAddresType.mweb, + network: network, + ); } return super.getChangeAddress(); diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index c659dd658..7cc266f5b 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart'; import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart'; import 'package:cw_core/encryption_file_utils.dart'; @@ -20,7 +21,7 @@ class LitecoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials, - BitcoinNewWalletCredentials> { + BitcoinRestoreWalletFromHardware> { LitecoinWalletService( this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect); @@ -147,9 +148,23 @@ class LitecoinWalletService extends WalletService< } @override - Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) { - throw UnimplementedError( - "Restoring a Litecoin wallet from a hardware wallet is not yet supported!"); + Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials, + {bool? isTestnet}) async { + final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; + credentials.walletInfo?.network = network.value; + credentials.walletInfo?.derivationInfo?.derivationPath = + credentials.hwAccountData.derivationPath; + + final wallet = await LitecoinWallet( + password: credentials.password!, + xpub: credentials.hwAccountData.xpub, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + await wallet.save(); + await wallet.init(); + return wallet; } @override diff --git a/cw_bitcoin/lib/psbt_transaction_builder.dart b/cw_bitcoin/lib/psbt_transaction_builder.dart index d8d2c9fac..81efb792e 100644 --- a/cw_bitcoin/lib/psbt_transaction_builder.dart +++ b/cw_bitcoin/lib/psbt_transaction_builder.dart @@ -16,10 +16,6 @@ class PSBTTransactionBuild { for (var i = 0; i < inputs.length; i++) { final input = inputs[i]; - print(input.utxo.isP2tr()); - print(input.utxo.isSegwit()); - print(input.utxo.isP2shSegwit()); - psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList())); psbt.setInputOutputIndex(i, input.utxo.vout); psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 36d762ea1..5cba9b734 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -101,6 +101,14 @@ packages: url: "https://github.com/cake-tech/blockchain_utils" source: git version: "3.3.0" + bluez: + dependency: transitive + description: + name: bluez + sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce" + url: "https://pub.dev" + source: hosted + version: "0.8.2" boolean_selector: dependency: transitive description: @@ -300,6 +308,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" encrypt: dependency: transitive description: @@ -361,19 +377,19 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1+1" - flutter_reactive_ble: - dependency: transitive - description: - name: flutter_reactive_ble - sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0" - url: "https://pub.dev" - source: hosted - version: "5.3.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_bluetooth: + dependency: transitive + description: + name: flutter_web_bluetooth + sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02" + url: "https://pub.dev" + source: hosted + version: "0.2.3" flutter_web_plugins: dependency: transitive description: flutter @@ -387,14 +403,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" - functional_data: - dependency: transitive - description: - name: functional_data - sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039" - url: "https://pub.dev" - source: hosted - version: "1.2.0" glob: dependency: transitive description: @@ -550,29 +558,37 @@ packages: ledger_bitcoin: dependency: "direct main" description: - path: "." + path: "packages/ledger-bitcoin" ref: HEAD - resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab - url: "https://github.com/cake-tech/ledger-bitcoin" + resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" source: git - version: "0.0.2" - ledger_flutter: + version: "0.0.3" + ledger_flutter_plus: dependency: "direct main" description: - path: "." - ref: cake-v3 - resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4" - url: "https://github.com/cake-tech/ledger-flutter.git" - source: git - version: "1.0.2" - ledger_usb: - dependency: transitive - description: - name: ledger_usb - sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2" + name: ledger_flutter_plus + sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.2.5" + ledger_litecoin: + dependency: "direct main" + description: + path: "packages/ledger-litecoin" + ref: HEAD + resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" + source: git + version: "0.0.2" + ledger_usb_plus: + dependency: transitive + description: + name: ledger_usb_plus + sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87" + url: "https://pub.dev" + source: hosted + version: "1.0.4" logging: dependency: transitive description: @@ -701,6 +717,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -773,30 +797,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" - reactive_ble_mobile: - dependency: transitive - description: - name: reactive_ble_mobile - sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798" - url: "https://pub.dev" - source: hosted - version: "5.3.1" - reactive_ble_platform_interface: - dependency: transitive - description: - name: reactive_ble_platform_interface - sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813" - url: "https://pub.dev" - source: hosted - version: "5.3.1" rxdart: dependency: "direct main" description: name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "0.27.7" + version: "0.28.0" shared_preferences: dependency: "direct main" description: @@ -987,6 +995,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_ble: + dependency: transitive + description: + name: universal_ble + sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3" + url: "https://pub.dev" + source: hosted + version: "0.12.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" unorm_dart: dependency: transitive description: @@ -1043,6 +1067,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 7e33d8260..9f1cee67d 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -24,16 +24,12 @@ dependencies: git: url: https://github.com/cake-tech/bitbox-flutter.git ref: Add-Support-For-OP-Return-data - rxdart: ^0.27.5 + rxdart: ^0.28.0 cryptography: ^2.0.5 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v2 - ledger_flutter: ^1.0.1 - ledger_bitcoin: - git: - url: https://github.com/cake-tech/ledger-bitcoin cw_mweb: path: ../cw_mweb grpc: ^3.2.4 @@ -44,6 +40,15 @@ dependencies: bech32: git: url: https://github.com/cake-tech/bech32.git + ledger_flutter_plus: ^1.4.1 + ledger_bitcoin: + git: + url: https://github.com/cake-tech/ledger-flutter-plus-plugins + path: packages/ledger-bitcoin + ledger_litecoin: + git: + url: https://github.com/cake-tech/ledger-flutter-plus-plugins + path: packages/ledger-litecoin dev_dependencies: flutter_test: @@ -54,10 +59,6 @@ dev_dependencies: hive_generator: ^1.1.3 dependency_overrides: - ledger_flutter: - git: - url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-v3 watcher: ^1.1.0 protobuf: ^3.1.0 bitcoin_base: diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 825c80d4a..d55914dcd 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -58,6 +58,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: network, initialAddressPageType: addressPageType, + isHardwareWallet: walletInfo.isHardwareWallet, ); autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart index 7342dc7f5..fe0ebc828 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -15,6 +15,7 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi required super.mainHd, required super.sideHd, required super.network, + required super.isHardwareWallet, super.initialAddresses, super.initialRegularAddressIndex, super.initialChangeAddressIndex, diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart index 99fd5b1f0..9a3069552 100644 --- a/cw_core/lib/hardware/device_connection_type.dart +++ b/cw_core/lib/hardware/device_connection_type.dart @@ -8,6 +8,7 @@ enum DeviceConnectionType { [bool isIOS = false]) { switch (walletType) { case WalletType.bitcoin: + case WalletType.litecoin: case WalletType.ethereum: case WalletType.polygon: if (isIOS) return [DeviceConnectionType.ble]; diff --git a/cw_evm/lib/evm_chain_hardware_wallet_service.dart b/cw_evm/lib/evm_chain_hardware_wallet_service.dart index 6f0d11f2e..d8f67c641 100644 --- a/cw_evm/lib/evm_chain_hardware_wallet_service.dart +++ b/cw_evm/lib/evm_chain_hardware_wallet_service.dart @@ -2,26 +2,26 @@ import 'dart:async'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:ledger_ethereum/ledger_ethereum.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; class EVMChainHardwareWalletService { - EVMChainHardwareWalletService(this.ledger, this.device); + EVMChainHardwareWalletService(this.ledgerConnection); - final Ledger ledger; - final LedgerDevice device; + final LedgerConnection ledgerConnection; - Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async { - final ethereumLedgerApp = EthereumLedgerApp(ledger); + Future<List<HardwareAccountData>> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection); - final version = await ethereumLedgerApp.getVersion(device); + await ethereumLedgerApp.getVersion(); final accounts = <HardwareAccountData>[]; final indexRange = List.generate(limit, (i) => i + index); for (final i in indexRange) { final derivationPath = "m/44'/60'/$i'/0/0"; - final address = - await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath); + final address = await ethereumLedgerApp.getAccounts( + accountsDerivationPath: derivationPath); accounts.add(HardwareAccountData( address: address.first, diff --git a/cw_evm/lib/evm_chain_transaction_credentials.dart b/cw_evm/lib/evm_chain_transaction_credentials.dart index 02927cb4d..5b5bdf170 100644 --- a/cw_evm/lib/evm_chain_transaction_credentials.dart +++ b/cw_evm/lib/evm_chain_transaction_credentials.dart @@ -1,7 +1,6 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; class EVMChainTransactionCredentials { EVMChainTransactionCredentials( diff --git a/cw_evm/lib/evm_ledger_credentials.dart b/cw_evm/lib/evm_ledger_credentials.dart index 0d8de1736..a0b7788dc 100644 --- a/cw_evm/lib/evm_ledger_credentials.dart +++ b/cw_evm/lib/evm_ledger_credentials.dart @@ -1,17 +1,16 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:cw_core/hardware/device_not_connected_exception.dart'; +import 'package:cw_core/hardware/device_not_connected_exception.dart' + as exception; import 'package:ledger_ethereum/ledger_ethereum.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:web3dart/crypto.dart'; import 'package:web3dart/web3dart.dart'; class EvmLedgerCredentials extends CredentialsWithKnownAddress { final String _address; - Ledger? ledger; - LedgerDevice? ledgerDevice; EthereumLedgerApp? ethereumLedgerApp; EvmLedgerCredentials(this._address); @@ -19,25 +18,25 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { @override EthereumAddress get address => EthereumAddress.fromHex(_address); - void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) { - ledger = setLedger; - ledgerDevice = setLedgerDevice; - ethereumLedgerApp = - EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0"); + void setLedgerConnection(LedgerConnection connection, + [String? derivationPath]) { + ethereumLedgerApp = EthereumLedgerApp(connection, + derivationPath: derivationPath ?? "m/44'/60'/0'/0/0"); } @override - MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) => - throw UnimplementedError("EvmLedgerCredentials.signToEcSignature"); + MsgSignature signToEcSignature(Uint8List payload, + {int? chainId, bool isEIP1559 = false}) => + throw UnimplementedError("EvmLedgerCredentials.signToEcSignature"); @override Future<MsgSignature> signToSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) async { - if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) { - throw DeviceNotConnectedException(); + if (ethereumLedgerApp == null) { + throw exception.DeviceNotConnectedException(); } - final sig = await ethereumLedgerApp!.signTransaction(device, payload); + final sig = await ethereumLedgerApp!.signTransaction(payload); final v = sig[0].toInt(); final r = bytesToHex(sig.sublist(1, 1 + 32)); @@ -65,14 +64,16 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity; } - return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV); + return MsgSignature( + BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV); } @override - Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async { - if (isNotConnected) throw DeviceNotConnectedException(); + Future<Uint8List> signPersonalMessage(Uint8List payload, + {int? chainId}) async { + if (isNotConnected) throw exception.DeviceNotConnectedException(); - final sig = await ethereumLedgerApp!.signMessage(device, payload); + final sig = await ethereumLedgerApp!.signMessage(payload); final r = sig.sublist(1, 1 + 32); final s = sig.sublist(1 + 32, 1 + 32 + 32); @@ -84,20 +85,22 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { @override Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) => - throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List"); + throw UnimplementedError( + "EvmLedgerCredentials.signPersonalMessageToUint8List"); - Future<void> provideERC20Info(String erc20ContractAddress, int chainId) async { - if (isNotConnected) throw DeviceNotConnectedException(); + Future<void> provideERC20Info( + String erc20ContractAddress, int chainId) async { + if (isNotConnected) throw exception.DeviceNotConnectedException(); try { - await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device, + await ethereumLedgerApp!.getAndProvideERC20TokenInformation( erc20ContractAddress: erc20ContractAddress, chainId: chainId); - } on LedgerException catch (e) { - if (e.errorCode != -28672) rethrow; + } catch (e) { + print(e); + rethrow; + // if (e.errorCode != -28672) rethrow; } } - bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null; - - LedgerDevice get device => ledgerDevice ?? ledger!.devices.first; + bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected; } diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index 3e12834b1..326ff4dc9 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -25,20 +25,17 @@ dependencies: mobx: ^2.0.7+4 cw_core: path: ../cw_core - ledger_flutter: ^1.0.1 + ledger_flutter_plus: ^1.4.1 ledger_ethereum: git: - url: https://github.com/cake-tech/ledger-ethereum.git + url: https://github.com/cake-tech/ledger-flutter-plus-plugins + path: packages/ledger-ethereum dependency_overrides: web3dart: git: url: https://github.com/cake-tech/web3dart.git ref: cake - ledger_flutter: - git: - url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-v3 watcher: ^1.1.0 dev_dependencies: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 365d86be5..60364c289 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -487,18 +487,30 @@ class CWBitcoin extends Bitcoin { } @override - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { - (wallet as BitcoinWallet).setLedger(ledger, device); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { + (wallet as ElectrumWallet).setLedgerConnection(connection); } @override - Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, + Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection); try { return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { - print(err.message); + } catch (err) { + print(err); + throw err; + } + } + + @override + Future<List<HardwareAccountData>> 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) { + print(err); throw err; } } diff --git a/lib/entities/hardware_wallet/hardware_wallet_device.dart b/lib/entities/hardware_wallet/hardware_wallet_device.dart new file mode 100644 index 000000000..d3acc5d32 --- /dev/null +++ b/lib/entities/hardware_wallet/hardware_wallet_device.dart @@ -0,0 +1,65 @@ +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; + +class HardwareWalletDevice { + final String name; + final HardwareWalletDeviceType type; + final HardwareWalletConnectionType connectionType; + + const HardwareWalletDevice({ + required this.name, + required this.type, + required this.connectionType, + }); + + factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) => + HardwareWalletDevice( + name: device.name, + type: device.deviceInfo.toGeneric(), + connectionType: device.connectionType.toGeneric(), + ); +} + +enum HardwareWalletDeviceType { + ledgerBlue, + ledgerNanoS, + ledgerNanoX, + ledgerNanoSPlus, + ledgerStax, + ledgerFlex; +} + +enum HardwareWalletConnectionType { + usb, + ble, + nfc; +} + +extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType { + HardwareWalletDeviceType toGeneric() { + switch (this) { + case ledger.LedgerDeviceType.blue: + return HardwareWalletDeviceType.ledgerBlue; + case ledger.LedgerDeviceType.nanoS: + return HardwareWalletDeviceType.ledgerNanoS; + case ledger.LedgerDeviceType.nanoSP: + return HardwareWalletDeviceType.ledgerNanoSPlus; + case ledger.LedgerDeviceType.nanoX: + return HardwareWalletDeviceType.ledgerNanoX; + case ledger.LedgerDeviceType.stax: + return HardwareWalletDeviceType.ledgerStax; + case ledger.LedgerDeviceType.flex: + return HardwareWalletDeviceType.ledgerFlex; + } + } +} + +extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType { + HardwareWalletConnectionType toGeneric() { + switch (this) { + case ledger.ConnectionType.usb: + return HardwareWalletConnectionType.usb; + case ledger.ConnectionType.ble: + return HardwareWalletConnectionType.ble; + } + } +} diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index e2c3d1358..7a06a1679 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -191,21 +191,21 @@ class CWEthereum extends Ethereum { String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger( - ledger, - device.connectionType == ConnectionType.usb ? device : null, - wallet.walletInfo.derivationInfo?.derivationPath); + void setLedgerConnection( + WalletBase wallet, ledger.LedgerConnection connection) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) + .setLedgerConnection( + connection, wallet.walletInfo.derivationInfo?.derivationPath); } @override Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection); try { return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { - print(err.message); + } catch (err) { + print(err); throw err; } } diff --git a/lib/main.dart b/lib/main.dart index 2b5bef06c..29b216b22 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/app_scroll_behavior.dart'; import 'package:cake_wallet/buy/order.dart'; @@ -43,6 +44,7 @@ import 'package:hive/hive.dart'; import 'package:cw_core/root_dir.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cw_core/window_size.dart'; +import 'package:logging/logging.dart'; final navigatorKey = GlobalKey<NavigatorState>(); final rootKey = GlobalKey<RootState>(); @@ -68,8 +70,18 @@ Future<void> runAppWithZone({Key? topLevelKey}) async { }; await initializeAppAtRoot(); - runApp(App(key: topLevelKey)); + if (kDebugMode) { + final appDocDir = await getAppDir(); + final ledgerFile = File('${appDocDir.path}/ledger_log.txt'); + if (!ledgerFile.existsSync()) ledgerFile.createSync(); + Logger.root.onRecord.listen((event) async { + final content = ledgerFile.readAsStringSync(); + ledgerFile.writeAsStringSync("$content\n${event.message}"); + }); + } + + runApp(App(key: topLevelKey)); isAppRunning = true; }, (error, stackTrace) async { if (!isAppRunning) { diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index 299c8278a..74b4026eb 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -190,20 +190,21 @@ class CWPolygon extends Polygon { String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger( - ledger, - device.connectionType == ConnectionType.usb ? device : null, - wallet.walletInfo.derivationInfo?.derivationPath); + void setLedgerConnection( + WalletBase wallet, ledger.LedgerConnection connection) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) + .setLedgerConnection( + connection, wallet.walletInfo.derivationInfo?.derivationPath); } @override Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection); try { return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { + } catch (err) { + print(err); throw err; } } diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index a482b1c41..9e331e818 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -3,15 +3,14 @@ import 'dart:io'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/screens/connect_device/debug_device_page.dart'; import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; import 'package:cake_wallet/themes/extensions/cake_text_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:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; -import 'package:permission_handler/permission_handler.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel); @@ -19,7 +18,8 @@ class ConnectDevicePageParams { final WalletType walletType; final OnConnectDevice onConnectDevice; - ConnectDevicePageParams({required this.walletType, required this.onConnectDevice}); + ConnectDevicePageParams( + {required this.walletType, required this.onConnectDevice}); } class ConnectDevicePage extends BasePage { @@ -35,7 +35,8 @@ class ConnectDevicePage extends BasePage { String get title => S.current.restore_title_from_hardware_wallet; @override - Widget body(BuildContext context) => ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM); + Widget body(BuildContext context) => + ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM); } class ConnectDevicePageBody extends StatefulWidget { @@ -43,47 +44,35 @@ class ConnectDevicePageBody extends StatefulWidget { final OnConnectDevice onConnectDevice; final LedgerViewModel ledgerVM; - const ConnectDevicePageBody(this.walletType, this.onConnectDevice, this.ledgerVM); + const ConnectDevicePageBody( + this.walletType, this.onConnectDevice, this.ledgerVM); @override ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState(); } class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { - final imageLedger = 'assets/images/ledger_nano.png'; - - final ledger = Ledger( - options: LedgerOptions( - scanMode: ScanMode.balanced, - maxScanDuration: const Duration(minutes: 5), - ), - onPermissionRequest: (_) async { - Map<Permission, PermissionStatus> statuses = await [ - Permission.bluetoothScan, - Permission.bluetoothConnect, - Permission.bluetoothAdvertise, - ].request(); - - return statuses.values.where((status) => status.isDenied).isEmpty; - }, - ); - - var bleIsEnabled = true; var bleDevices = <LedgerDevice>[]; var usbDevices = <LedgerDevice>[]; late Timer? _usbRefreshTimer = null; late Timer? _bleRefreshTimer = null; + late Timer? _bleStateTimer = null; late StreamSubscription<LedgerDevice>? _bleRefresh = null; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); + _bleStateTimer = Timer.periodic( + Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState()); + + _bleRefreshTimer = + Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); if (Platform.isAndroid) { - _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); + _usbRefreshTimer = + Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); } }); } @@ -91,35 +80,59 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { @override void dispose() { _bleRefreshTimer?.cancel(); + _bleStateTimer?.cancel(); _usbRefreshTimer?.cancel(); _bleRefresh?.cancel(); super.dispose(); } Future<void> _refreshUsbDevices() async { - final dev = await ledger.listUsbDevices(); + final dev = await widget.ledgerVM.ledgerPlusUSB.devices; 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<void> _refreshBleDevices() async { try { - _bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device))) + _bleRefresh = widget.ledgerVM + .scanForBleDevices() + .listen((device) => setState(() => bleDevices.add(device))) ..onError((e) { throw e.toString(); }); - setState(() => bleIsEnabled = true); _bleRefreshTimer?.cancel(); _bleRefreshTimer = null; } catch (e) { - setState(() => bleIsEnabled = false); + print(e); } } Future<void> _connectToDevice(LedgerDevice device) async { - await widget.ledgerVM.connectLedger(device); + await widget.ledgerVM.connectLedger(device, widget.walletType); widget.onConnectDevice(context, widget.ledgerVM); } + String _getDeviceTileLeading(LedgerDeviceType deviceInfo) { + switch (deviceInfo) { + case LedgerDeviceType.nanoX: + return 'assets/images/hardware_wallet/ledger_nano_x.png'; + case LedgerDeviceType.stax: + return 'assets/images/hardware_wallet/ledger_stax.png'; + case LedgerDeviceType.flex: + return 'assets/images/hardware_wallet/ledger_flex.png'; + default: + return 'assets/images/hardware_wallet/ledger_nano_x.png'; + } + } + @override Widget build(BuildContext context) { return Center( @@ -139,7 +152,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor), + color: Theme.of(context) + .extension<CakeTextTheme>()! + .titleColor), textAlign: TextAlign.center, ), ), @@ -152,18 +167,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { // title: "Debug Ledger", // leading: imageLedger, // ), - if (!bleIsEnabled) - Padding( - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), - child: Text( - S.of(context).ledger_please_enable_bluetooth, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor), - textAlign: TextAlign.center, + Observer( + builder: (_) => Offstage( + offstage: widget.ledgerVM.bleIsEnabled, + child: Padding( + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), + child: Text( + S.of(context).ledger_please_enable_bluetooth, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .extension<CakeTextTheme>()! + .titleColor), + textAlign: TextAlign.center, + ), ), ), + ), + if (bleDevices.length > 0) ...[ Padding( padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), @@ -174,7 +196,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, + color: Theme.of(context) + .extension<CakeTextTheme>()! + .titleColor, ), ), ), @@ -186,7 +210,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { child: DeviceTile( onPressed: () => _connectToDevice(device), title: device.name, - leading: imageLedger, + leading: _getDeviceTileLeading(device.deviceInfo), connectionType: device.connectionType, ), ), @@ -203,7 +227,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, + color: Theme.of(context) + .extension<CakeTextTheme>()! + .titleColor, ), ), ), @@ -215,7 +241,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { child: DeviceTile( onPressed: () => _connectToDevice(device), title: device.name, - leading: imageLedger, + leading: _getDeviceTileLeading(device.deviceInfo), connectionType: device.connectionType, ), ), diff --git a/lib/src/screens/connect_device/debug_device_page.dart b/lib/src/screens/connect_device/debug_device_page.dart index f5a9ef2a4..bed9d59a7 100644 --- a/lib/src/screens/connect_device/debug_device_page.dart +++ b/lib/src/screens/connect_device/debug_device_page.dart @@ -1,15 +1,15 @@ -// import 'dart:convert'; +// import 'dart:typed_data'; // +// import 'package:basic_utils/basic_utils.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; // import 'package:cake_wallet/src/screens/base_page.dart'; // import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; // import 'package:cake_wallet/src/widgets/primary_button.dart'; // import 'package:cake_wallet/utils/responsive_layout_util.dart'; -// import 'package:convert/convert.dart'; // import 'package:flutter/material.dart'; -// import 'package:ledger_bitcoin/ledger_bitcoin.dart'; -// import 'package:ledger_flutter/ledger_flutter.dart'; +// import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; +// import 'package:ledger_litecoin/ledger_litecoin.dart'; // import 'package:permission_handler/permission_handler.dart'; -// import 'package:polyseed/polyseed.dart'; // // class DebugDevicePage extends BasePage { // @override @@ -50,7 +50,9 @@ // }, // ); // -// late BitcoinLedgerApp btc; +// // late BitcoinLedgerApp btc; +// late LitecoinLedgerApp ltc; +// // var devices = <LedgerDevice>[]; // var status = ""; // var counter = 0; @@ -59,7 +61,8 @@ // @override // void initState() { // super.initState(); -// btc = BitcoinLedgerApp(ledger); +// // btc = BitcoinLedgerApp(ledger); +// ltc = LitecoinLedgerApp(ledger); // } // // @override @@ -81,7 +84,7 @@ // // @override // Widget build(BuildContext context) { -// final imageLedger = 'assets/images/ledger_nano.png'; +// final imageLedger = 'assets/images/hardware_wallet/ledger_nano_x.png'; // // return Center( // child: Container( @@ -99,40 +102,25 @@ // DebugButton( // title: "Get Version", // method: "Version", -// func: () async => await btc.getVersion(selectedDevice!), -// ), -// DebugButton( -// title: "Get Master Fingerprint", -// method: "Master Fingerprint", -// func: () async => hex.encode(await btc.getMasterFingerprint(selectedDevice!)), -// ), -// DebugButton( -// title: "Get XPub", -// method: "XPub", -// func: () async => await btc.getXPubKey(selectedDevice!, derivationPath: "m/84'/0'/$counter'"), +// // func: () async => await btc.getVersion(selectedDevice!), +// func: () async => await ltc.getVersion(selectedDevice!), // ), // DebugButton( // title: "Get Wallet Address", // method: "Wallet Address", // func: () async { // setState(() => counter++); -// final derivationPath = "m/84'/0'/$counter'/0/0"; -// return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath); +// final derivationPath = "m/84'/2'/0'/0/0"; +// return await ltc.getAccounts(selectedDevice!, +// accountsDerivationPath: derivationPath); +// // return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath); // // return await ethereum!.getHardwareWalletAccounts(selectedDevice!); -// }, +// }, // ), // DebugButton( // title: "Send Money", -// method: "Sig", -// func: () async { -// final psbt = PsbtV2(); -// final psbtBuf = base64.decode( -// "cHNidP8BAgQCAAAAAQQBAQEFAQIAAQ4gTW6k/cwKKu1u7m9oKr5ob7VcAC0IPkfaDitRi/FkD7sBDwQAAAAAARAE/////wEA/ekBAQAAAAABA9AYVQLI722H0osKMa/4dvMucrnKV1Myxtlp0l0BoOBDAQAAAAD/////ku6r2ABaHt9N26f/P4eMljX8t1f4lBcFfEwuNm/uXYoBAAAAAP////+YeAl8arEGKOcyrWJAYwSboyCstkhHN8zn7/vy7pkYTAEAAAAA/////wHlHgAAAAAAABYAFKdq0umSucBGVkl2MpT6Hgo/0a/xAkcwRAIgMkiJmNFbEi2I3CQYOwyV/JepCnFQRvj4xghkySpFcJMCIGAypkkWltfj+ucvqUIu27tusDAIAAB+rBhX/GV7hPlEASEDyLmWyTLjLfC9kn8pnW42jW5N6EJo5fObjWWEyfLDu9UCSDBFAiEAg9crVtwBPF+sWk+Th6pLwzDjJGItwsUCvoBPtmMTEb4CIDGuM7WOguV0TP21oidF3bSUZlEAjUHWfWzxLKw+3LofASEDfN16xKb70UZSeQyX5Tlh8iRq7np5Nlz9GYdcSU50sKwCSDBFAiEAvotOblaEiBptRWkvb6bj2MGyRjTphKLBLiHYmrRMTCgCIEKJH+z65uPSSz1NIb0d/u3bU9l0xcWk0idEsXjB+BIiASEDrAEiEtrSNKxbh6F/KPaCTafF2LVjCzb75WB+x4xSuoQAAAAAAQEf5R4AAAAAAAAWABSnatLpkrnARlZJdjKU+h4KP9Gv8SIGA3xMuxmPsBAm9aMEUBs3N46DB+Kdts3bZR/Wxt+uM0H4GKtN6bpUAACAAAAAgAAAAIAAAAAAAAAAAAABBBTk7bEOxYcdXDi1eeWraYDufm6eJgEDCOgDAAAAAAAAAAEEFDX3g/pnDXIfsRw8shK42NZn+SdpAQMIiBMAAAAAAAAiAgN8TLsZj7AQJvWjBFAbNzeOgwfinbbN22Uf1sbfrjNB+BirTem6VAAAgAAAAIAAAACAAAAAAAAAAAAA" -// ); -// psbt.deserialize(psbtBuf); -// final result = await btc.signPsbt(selectedDevice!, psbt: psbt); -// return result.toHexString(); -// }, +// method: "Raw Tx", +// func: sendMoney // ), // Padding( // padding: EdgeInsets.only(top: 20), @@ -147,18 +135,18 @@ // ...devices // .map( // (device) => Padding( -// padding: EdgeInsets.only(bottom: 20), -// child: DeviceTile( -// onPressed: () { -// setState(() => selectedDevice = device); -// ledger.connect(device); -// }, -// title: device.name, -// leading: imageLedger, -// connectionType: device.connectionType, -// ), -// ), -// ) +// padding: EdgeInsets.only(bottom: 20), +// child: DeviceTile( +// onPressed: () { +// setState(() => selectedDevice = device); +// ledger.connect(device); +// }, +// title: device.name, +// leading: imageLedger, +// connectionType: device.connectionType, +// ), +// ), +// ) // .toList(), // PrimaryButton( // text: "Refresh BLE", @@ -188,6 +176,42 @@ // ); // } // +// Future<String> sendMoney() async { +// final readyInputs = [ +// LedgerTransaction( +// rawTx: "010000000001018c055c85c3724c98842d27712771dd0de139711f5940bba2df4615c5522184740000000017160014faf7f6dfb4e70798b92c93f33b4c51024491829df0ffffff022b05c70000000000160014f489f947fd13a1fb44ac168427081d3f30b6ce0cde9dd82e0000000017a914d5eca376cb49d65031220ff9093b7d407073ed0d8702483045022100f648c9f6a9b8f35b6ec29bbfae312c95ed3d56ce6a3f177d994efe90562ec4bd02205b82ce2c94bc0c9d152c3afc668b200bd82f48d6a14e83c66ba0f154cd5f69190121038f1dca119420d4aa7ad04af1c0d65304723789cccc56d335b18692390437f35900000000", +// outputIndex: 0, +// ownerPublicKey: +// HexUtils.decode("03b2e67958ed3356e329e05cf94c3bee6b20c17175ac3b2a1278e073bf44f5d6ec"), +// ownerDerivationPath: "m/84'/2'/0'/0/0", +// sequence: 0xffffffff, +// ) +// ]; +// +// final outputs = [ +// BitcoinOutput( +// address: P2wpkhAddress.fromAddress( +// address: "ltc1qn0g5e36xaj07lqj6w9xn52ng07hud42g3jf5ps", +// network: LitecoinNetwork.mainnet), +// value: BigInt.from(1000000)), +// BitcoinOutput( +// address: P2wpkhAddress.fromAddress( +// address: "ltc1qrx29qz4ghu4j0xk37ptgk7034cwpmjyxhrcnk9", +// network: LitecoinNetwork.mainnet), +// value: BigInt.from(12042705)), +// ]; +// return await ltc.createTransaction(selectedDevice!, +// inputs: readyInputs, +// outputs: outputs +// .map((e) => TransactionOutput.fromBigInt( +// e.value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) +// .toList(), +// sigHashType: 0x01, +// additionals: ["bech32"], +// isSegWit: true, +// useTrustedInputForSegwit: true); +// } +// // Widget DebugButton( // {required String title, required String method, required Future<dynamic> Function() func}) { // return Padding( diff --git a/lib/src/screens/connect_device/widgets/device_tile.dart b/lib/src/screens/connect_device/widgets/device_tile.dart index 8367d1606..58f65c5de 100644 --- a/lib/src/screens/connect_device/widgets/device_tile.dart +++ b/lib/src/screens/connect_device/widgets/device_tile.dart @@ -1,6 +1,6 @@ import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:flutter/material.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; class DeviceTile extends StatelessWidget { const DeviceTile({ diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 478f1a3c3..028b3cfcf 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -188,7 +188,7 @@ class CryptoBalanceWidget extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: Image.asset( - 'assets/images/ledger_nano.png', + 'assets/images/hardware_wallet/ledger_nano_x.png', width: 24, color: Theme.of(context) .extension<DashboardPageTheme>()! diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index d671230c4..57f5ec727 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -67,7 +67,7 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor; final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor; final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor; - final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor); + final imageLedger = Image.asset('assets/images/hardware_wallet/ledger_nano_x.png', width: 40, color: imageColor); final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor); final imageBackup = Image.asset('assets/images/backup.png', color: imageColor); @@ -186,4 +186,4 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { } } } -} \ No newline at end of file +} diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index d881d0341..bce82312d 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -395,16 +395,19 @@ class SendPage extends BasePage { if (sendViewModel.wallet.isHardwareWallet) { if (!sendViewModel.ledgerViewModel!.isConnected) { - await Navigator.of(context).pushNamed(Routes.connectDevices, + await Navigator.of(context).pushNamed( + Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: sendViewModel.walletType, onConnectDevice: (BuildContext context, _) { - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet); + sendViewModel.ledgerViewModel! + .setLedger(sendViewModel.wallet); Navigator.of(context).pop(); }, )); } else { - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet); + sendViewModel.ledgerViewModel! + .setLedger(sendViewModel.wallet); } } @@ -509,7 +512,7 @@ class SendPage extends BasePage { if (state is TransactionCommitted) { newContactAddress = newContactAddress ?? sendViewModel.newContactAddress(); - + if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) { newContactAddress = null; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 26bc5ceb9..53a8277ab 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -435,7 +435,7 @@ abstract class DashboardViewModelBase with Store { } @computed - bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid); + bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid) && !wallet.isHardwareWallet; @computed bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled; diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index f05b1c805..19b190fe3 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; @@ -9,11 +10,19 @@ import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/hardware/device_connection_type.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; + +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk; +import 'package:mobx/mobx.dart'; import 'package:permission_handler/permission_handler.dart'; -class LedgerViewModel { - late final Ledger ledger; +part 'ledger_view_model.g.dart'; + +class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel; + +abstract class LedgerViewModelBase with Store { + // late final Ledger ledger; + late final sdk.LedgerInterface ledgerPlusBLE; + late final sdk.LedgerInterface ledgerPlusUSB; bool get _doesSupportHardwareWallets { if (!DeviceInfo.instance.isMobile) { @@ -21,53 +30,97 @@ class LedgerViewModel { } if (isMoneroOnly) { - return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS) + return DeviceConnectionType.supportedConnectionTypes( + WalletType.monero, Platform.isIOS) .isNotEmpty; } return true; } - LedgerViewModel() { + LedgerViewModelBase() { if (_doesSupportHardwareWallets) { - ledger = Ledger( - options: LedgerOptions( - scanMode: ScanMode.balanced, - maxScanDuration: const Duration(minutes: 5), - ), - onPermissionRequest: (_) async { - Map<Permission, PermissionStatus> statuses = await [ - Permission.bluetoothScan, - Permission.bluetoothConnect, - Permission.bluetoothAdvertise, - ].request(); + reaction((_) => bleIsEnabled, (_) { + if (bleIsEnabled) _initBLE(); + }); + updateBleState(); - return statuses.values.where((status) => status.isDenied).isEmpty; - }, - ); + if (!Platform.isIOS) { + ledgerPlusUSB = sdk.LedgerInterface.usb(); + } } } - Future<void> connectLedger(LedgerDevice device) async { - await ledger.connect(device); + @observable + bool bleIsEnabled = false; - if (device.connectionType == ConnectionType.usb) _device = device; + bool _bleIsInitialized = false; + Future<void> _initBLE() async { + if (bleIsEnabled && !_bleIsInitialized) { + ledgerPlusBLE = sdk.LedgerInterface.ble(onPermissionRequest: (_) async { + Map<Permission, PermissionStatus> statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.bluetoothAdvertise, + ].request(); + + return statuses.values.where((status) => status.isDenied).isEmpty; + }); + _bleIsInitialized = true; + } } - LedgerDevice? _device; + Future<void> updateBleState() async { + final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState(); - bool get isConnected => ledger.devices.isNotEmpty || _device != null; + final newState = bleState == sdk.AvailabilityState.poweredOn; - LedgerDevice get device => _device ?? ledger.devices.first; + if (newState != bleIsEnabled) bleIsEnabled = newState; + } + + Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan(); + + Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan(); + + Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async { + if (isConnected) { + try { + await _connection!.disconnect(); + } catch (_) {} + } + final ledger = device.connectionType == sdk.ConnectionType.ble + ? ledgerPlusBLE + : ledgerPlusUSB; + + if (_connectionChangeListener == null) { + _connectionChangeListener = ledger.deviceStateChanges.listen((event) { + print('Ledger Device State Changed: $event'); + if (event == sdk.BleConnectionState.disconnected) { + _connection = null; + _connectionChangeListener?.cancel(); + } + }); + } + + _connection = await ledger.connect(device); + } + + StreamSubscription<sdk.BleConnectionState>? _connectionChangeListener; + sdk.LedgerConnection? _connection; + + bool get isConnected => _connection != null && !(_connection!.isDisconnected); + + sdk.LedgerConnection get connection => _connection!; void setLedger(WalletBase wallet) { switch (wallet.type) { case WalletType.bitcoin: - return bitcoin!.setLedger(wallet, ledger, device); + case WalletType.litecoin: + return bitcoin!.setLedgerConnection(wallet, connection); case WalletType.ethereum: - return ethereum!.setLedger(wallet, ledger, device); + return ethereum!.setLedgerConnection(wallet, connection); case WalletType.polygon: - return polygon!.setLedger(wallet, ledger, device); + return polygon!.setLedgerConnection(wallet, connection); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 4064a1258..24e83f364 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -24,7 +24,6 @@ import 'package:cw_core/unspent_coin_type.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/core/address_validator.dart'; @@ -408,16 +407,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor state = ExecutedSuccessfullyState(); return pendingTransaction; } catch (e) { - if (e is LedgerException) { - final errorCode = e.errorCode.toRadixString(16); - final fallbackMsg = - e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode"; - final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg; - - state = FailureState(errorMsg); - } else { + // if (e is LedgerException) { + // final errorCode = e.errorCode.toRadixString(16); + // final fallbackMsg = + // e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode"; + // final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg; + // + // state = FailureState(errorMsg); + // } else { state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); - } + // } } return null; } diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index 68bc95a00..91e0de685 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -13,7 +13,6 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:mobx/mobx.dart'; part 'wallet_hardware_restore_view_model.g.dart'; @@ -58,7 +57,11 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with switch (type) { case WalletType.bitcoin: accounts = await bitcoin! - .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit); + .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! @@ -74,9 +77,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with availableAccounts.addAll(accounts); _nextIndex += limit; - } on LedgerException catch (e) { - error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16)); + // } on LedgerException catch (e) { + // error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16)); } catch (e) { + print(e); error = S.current.ledger_connection_error; } @@ -89,6 +93,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with WalletCredentials credentials; switch (type) { case WalletType.bitcoin: + case WalletType.litecoin: credentials = bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!); break; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 86b3462ac..9895e6305 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -17,6 +17,7 @@ import package_info_plus import path_provider_foundation import share_plus import shared_preferences_foundation +import universal_ble import url_launcher_macos import wakelock_plus @@ -33,6 +34,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UniversalBlePlugin.register(with: registry.registrar(forPlugin: "UniversalBlePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index a2f19e596..d5fce76e9 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -12,7 +12,7 @@ dependencies: version: 4.0.2 shared_preferences: ^2.0.15 # provider: ^6.0.3 - rxdart: ^0.27.4 + rxdart: ^0.28.0 yaml: ^3.1.1 #barcode_scan: any barcode_scan2: ^4.2.1 @@ -97,7 +97,7 @@ dependencies: polyseed: ^0.0.6 nostr_tools: ^1.0.9 solana: ^0.30.1 - ledger_flutter: ^1.0.1 + ledger_flutter_plus: ^1.4.1 hashlib: ^1.19.2 dev_dependencies: @@ -125,10 +125,6 @@ dependency_overrides: bech32: git: url: https://github.com/cake-tech/bech32.git - ledger_flutter: - git: - url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-v3 web3dart: git: url: https://github.com/cake-tech/web3dart.git @@ -155,6 +151,7 @@ flutter: assets: - assets/images/ - assets/images/flags/ + - assets/images/hardware_wallet/ - assets/node_list.yml - assets/haven_node_list.yml - assets/bitcoin_electrum_server_list.yml diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 80433ae0e..2ec59f349 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -97,7 +97,7 @@ "cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!", "cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.", "cake_pay_save_order": "Die Karte sollte innerhalb von 1 Werktag an Ihre E-Mail gesendet werden, \n Ihre Bestell-ID zu speichern:", - "cake_pay_subtitle": "Kaufen Sie weltweite Prepaid -Karten und Geschenkkarten", + "cake_pay_subtitle": "Kaufen Sie weltweite Prepaid-Karten und Geschenkkarten", "cake_pay_web_cards_subtitle": "Kaufen Sie weltweit Prepaid-Karten und Geschenkkarten", "cake_pay_web_cards_title": "Cake Pay-Webkarten", "cake_wallet": "Cake Wallet", @@ -114,7 +114,7 @@ "change_currency": "Währung ändern", "change_current_node": "Möchten Sie den aktuellen Knoten wirklich zu ${node}? ändern?", "change_current_node_title": "Aktuellen Knoten ändern", - "change_exchange_provider": "Swap -Anbieter ändern", + "change_exchange_provider": "Swap-Anbieter ändern", "change_language": "Sprache ändern", "change_language_to": "Sprache zu ${language} ändern?", "change_password": "Passwort ändern", @@ -131,7 +131,7 @@ "choose_one": "Wähle ein", "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 Brieftaschengruppe", + "choose_wallet_group": "Wählen Sie Walletgruppe", "clear": "Zurücksetzen", "clearnet_link": "Clearnet-Link", "close": "Schließen", @@ -179,7 +179,7 @@ "create_invoice": "Rechnung erstellen", "create_new": "Neue Wallet erstellen", "create_new_account": "Neues Konto erstellen", - "create_new_seed": "Neue Samen erstellen", + "create_new_seed": "Neue Seed erstellen", "creating_new_wallet": "Neue Wallet erstellen", "creating_new_wallet_error": "Fehler: ${description}", "creation_date": "Erstellungsdatum", @@ -189,9 +189,9 @@ "custom_value": "Benutzerdefinierten Wert", "dark_theme": "Dunkel", "debit_card": "Debitkarte", - "debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Geldbörse unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.", + "debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Wallet unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.", "decimal_places_error": "Zu viele Nachkommastellen", - "decimals_cannot_be_zero": "Token -Dezimalzahl kann nicht Null sein.", + "decimals_cannot_be_zero": "Token-Dezimalzahl kann nicht Null sein.", "default_buy_provider": "Standard-Kaufanbieter", "default_sell_provider": "Standard-Verkaufsanbieter", "delete": "Löschen", @@ -236,7 +236,7 @@ "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", "email_address": "E-Mail-Adresse", "enable": "Aktivieren", - "enable_mempool_api": "Mempool -API für genaue Gebühren und Daten", + "enable_mempool_api": "Mempool-API für genaue Gebühren und Daten", "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee", "enable_silent_payments_scanning": "Scannen Sie stille Zahlungen, bis die Spitze erreicht ist", "enabled": "Ermöglicht", @@ -245,7 +245,7 @@ "enter_code": "Code eingeben", "enter_seed_phrase": "Geben Sie Ihre Seed-Phrase ein", "enter_totp_code": "Bitte geben Sie den TOTP-Code ein.", - "enter_wallet_password": "Geben Sie das Brieftaschenkennwort ein", + "enter_wallet_password": "Geben Sie das Walletkennwort ein", "enter_your_note": "Geben Sie Ihre Bemerkung ein…", "enter_your_pin": "PIN eingeben", "enter_your_pin_again": "Geben Sie Ihre PIN erneut ein", @@ -282,7 +282,7 @@ "event": "Ereignis", "events": "Veranstaltungen", "exchange": "Tauschen", - "exchange_incorrect_current_wallet_for_xmr": "Wenn Sie XMR aus Ihrer Cake -Wallet Monero -Balance austauschen möchten, wechseln Sie zuerst zu Ihrer Monero -Brieftasche.", + "exchange_incorrect_current_wallet_for_xmr": "Wenn Sie XMR aus Ihrer CakeWallet Monero-Balance tauschen möchten, wechseln Sie zuerst zu Ihrer Monero-Wallet.", "exchange_new_template": "Neue Vorlage", "exchange_provider_unsupported": "${providerName} wird nicht mehr unterstützt!", "exchange_result_confirm": "Durch Drücken von \"Bestätigen\" wird ${fetchingLabel} ${from} von Ihrer Wallet namens ${walletName} an die unten angegebene Adresse gesendet. Alternativ können Sie von einer externen Wallet an die unten angegebene Adresse / QR-Code senden.\n\nBitte bestätigen Sie, um fortzufahren, oder gehen Sie zurück, um die Beträge zu ändern.", @@ -309,7 +309,7 @@ "fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben", "filter_by": "Filtern nach", "first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Ethereum, Litecoin, und Haven", - "fixed_pair_not_supported": "Dieses feste Paar wird nicht von den ausgewählten Swap -Diensten unterstützt", + "fixed_pair_not_supported": "Dieses feste Paar wird nicht von den ausgewählten Swap-Diensten unterstützt", "fixed_rate": "Feste Rate", "fixed_rate_alert": "Sie können den Empfangsbetrag eingeben, wenn der Festratenmodus aktiviert ist. Möchten Sie in den Festratenmodus wechseln?", "forgot_password": "Passwort vergessen", @@ -349,9 +349,9 @@ "incoming": "Eingehend", "incorrect_seed": "Der eingegebene Text ist ungültig.", "inputs": "Eingänge", - "insufficient_lamport_for_tx": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Bitte fügen Sie Ihrer Brieftasche mehr Sol hinzu oder reduzieren Sie die SO -Menge, die Sie senden.", - "insufficient_lamports": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Sie brauchen mindestens ${solValueNeeded} Sol. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol -Betrag", - "insufficientFundsForRentError": "Sie haben nicht genug SOL, um die Transaktionsgebühr und die Miete für das Konto zu decken. Bitte fügen Sie mehr Sol zu Ihrer Brieftasche hinzu oder reduzieren Sie den von Ihnen gesendeten Sol -Betrag", + "insufficient_lamport_for_tx": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Bitte fügen Sie Ihrer Wallet mehr Sol hinzu oder reduzieren Sie die SOL-Menge, die Sie senden.", + "insufficient_lamports": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Sie brauchen mindestens ${solValueNeeded} Sol. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol-Betrag", + "insufficientFundsForRentError": "Sie haben nicht genug SOL, um die Transaktionsgebühr und die Miete für das Konto zu decken. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol-Betrag", "introducing_cake_pay": "Einführung von Cake Pay!", "invalid_input": "Ungültige Eingabe", "invalid_password": "Ungültiges Passwort", @@ -365,20 +365,20 @@ "ledger_error_wrong_app": "Bitte stellen Sie sicher, dass Sie die richtige App auf Ihrem Ledger geöffnet haben", "ledger_please_enable_bluetooth": "Bitte aktivieren Sie Bluetooth um sich mit Ihren Ledger zu verbinden.", "light_theme": "Hell", - "litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB -Scannen", + "litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB-Scannen", "litecoin_mweb": "MWeb", "litecoin_mweb_always_scan": "Setzen Sie MWeb immer scannen", - "litecoin_mweb_description": "MWEB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt", + "litecoin_mweb_description": "MWWB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt", "litecoin_mweb_dismiss": "Zurückweisen", - "litecoin_mweb_display_card": "MWEB -Karte anzeigen", + "litecoin_mweb_display_card": "MWEB-Karte anzeigen", "litecoin_mweb_enable_later": "Sie können MWEB unter Anzeigeeinstellungen erneut aktivieren.", "litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegout": "Abstecken", "litecoin_mweb_scanning": "MWEB Scanning", - "litecoin_mweb_settings": "MWEB -Einstellungen", - "litecoin_mweb_warning": "Durch die Verwendung von MWEB wird zunächst ~ 600 MB Daten heruntergeladen und kann je nach Netzwerkgeschwindigkeit bis zu 30 Minuten dauern. Diese ersten Daten werden nur einmal heruntergeladen und für alle Litecoin -Brieftaschen verfügbar", + "litecoin_mweb_settings": "MWEB-Einstellungen", + "litecoin_mweb_warning": "Durch die Verwendung von MWEB wird zunächst ~ 600 MB Daten heruntergeladen und kann je nach Netzwerkgeschwindigkeit bis zu 30 Minuten dauern. Diese ersten Daten werden nur einmal heruntergeladen und für alle Litecoin-Wallets verfügbar", "litecoin_what_is_mweb": "Was ist MWeb?", - "live_fee_rates": "Live -Gebührenpreise über API", + "live_fee_rates": "Live-Gebührenpreise über API", "load_more": "Mehr laden", "loading_your_wallet": "Wallet wird geladen", "login": "Einloggen", @@ -533,8 +533,8 @@ "rename": "Umbenennen", "rep_warning": "Repräsentative Warnung", "rep_warning_sub": "Ihr Vertreter scheint nicht gut zu sein. Tippen Sie hier, um eine neue auszuwählen", - "repeat_wallet_password": "Wiederholen Sie das Brieftaschenkennwort", - "repeated_password_is_incorrect": "Wiederholtes Passwort ist falsch. Bitte wiederholen Sie das Brieftaschenkennwort erneut.", + "repeat_wallet_password": "Wiederholen Sie das Walletkennwort", + "repeated_password_is_incorrect": "Wiederholtes Passwort ist falsch. Bitte wiederholen Sie das Walletkennwort erneut.", "require_for_adding_contacts": "Erforderlich zum Hinzufügen von Kontakten", "require_for_all_security_and_backup_settings": "Für alle Sicherheits- und Sicherungseinstellungen erforderlich", "require_for_assessing_wallet": "Für den Zugriff auf die Wallet erforderlich", @@ -576,7 +576,7 @@ "restore_wallet": "Wallet wiederherstellen", "restore_wallet_name": "Walletname", "restore_wallet_restore_description": "Beschreibung zur Wallet-Wiederherstellung", - "robinhood_option_description": "Kaufen und übertragen Sie sofort mit Ihrem Debitkarten-, Bankkonto- oder Robinhood -Guthaben. Nur USA.", + "robinhood_option_description": "Kaufen und übertragen Sie sofort mit Ihrem Debitkarten-, Bankkonto- oder Robinhood-Guthaben. Nur USA.", "router_no_route": "Keine Route definiert für ${name}", "save": "Speichern", "save_backup_password": "Bitte stellen Sie sicher, dass Sie Ihr Sicherungskennwort gespeichert haben. Ohne dieses können Sie Ihre Sicherungsdateien nicht importieren.", @@ -622,8 +622,8 @@ "seed_share": "Seed teilen", "seed_title": "Seed", "seedtype": "Seedtyp", - "seedtype_alert_content": "Das Teilen von Samen mit anderen Brieftaschen ist nur mit bip39 Seedype möglich.", - "seedtype_alert_title": "Seedype -Alarm", + "seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.", + "seedtype_alert_title": "Seedype-Alarm", "seedtype_legacy": "Veraltet (25 Wörter)", "seedtype_polyseed": "Polyseed (16 Wörter)", "seedtype_wownero": "WOWNO (14 Wörter)", @@ -691,7 +691,7 @@ "setup_your_debit_card": "Richten Sie Ihre Debitkarte ein", "share": "Teilen", "share_address": "Adresse teilen ", - "shared_seed_wallet_groups": "Gemeinsame Samenbrieftaschengruppen", + "shared_seed_wallet_groups": "Gemeinsame Walletsseed Gruppen", "show_details": "Details anzeigen", "show_keys": "Seed/Schlüssel anzeigen", "show_market_place": "Marktplatz anzeigen", @@ -711,12 +711,12 @@ "silent_payments_disclaimer": "Neue Adressen sind keine neuen Identitäten. Es ist eine Wiederverwendung einer bestehenden Identität mit einem anderen Etikett.", "silent_payments_display_card": "Zeigen Sie stille Zahlungskarte", "silent_payments_scan_from_date": "Scan ab Datum", - "silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Brieftasche jeden Block scannt oder nur die angegebene Höhe überprüft.", + "silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Wallet jeden Block scannt oder nur die angegebene Höhe überprüft.", "silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen", "silent_payments_scanned_tip": "Gescannt zum Trinkgeld! (${tip})", "silent_payments_scanning": "Stille Zahlungen scannen", "silent_payments_settings": "Einstellungen für stille Zahlungen", - "single_seed_wallets_group": "Einzelne Samenbriefen", + "single_seed_wallets_group": "Einzelne Wallets", "slidable": "Verschiebbar", "sort_by": "Sortiere nach", "spend_key_private": "Spend Key (geheim)", @@ -755,11 +755,11 @@ "syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert", "template": "Vorlage", "template_name": "Vorlagenname", - "testnet_coins_no_value": "Testnet -Münzen haben keinen Wert", + "testnet_coins_no_value": "Testnet-Münzen haben keinen Wert", "third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!", "third_intro_title": "Yat spielt gut mit anderen", "thorchain_contract_address_not_supported": "Thorchain unterstützt das Senden an eine Vertragsadresse nicht", - "thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.", + "thorchain_taproot_address_not_supported": "Der Thorchain-Anbieter unterstützt keine Taproot-Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.", "time": "${minutes}m ${seconds}s", "tip": "Hinweis:", "today": "Heute", @@ -879,12 +879,12 @@ "voting_weight": "Stimmgewicht", "waitFewSecondForTxUpdate": "Bitte warten Sie einige Sekunden, bis die Transaktion im Transaktionsverlauf angezeigt wird", "waiting_payment_confirmation": "Warte auf Zahlungsbestätigung", - "wallet_group": "Brieftaschengruppe", - "wallet_group_description_four": "eine Brieftasche mit einem völlig neuen Samen schaffen.", - "wallet_group_description_one": "In Kuchenbrieftasche können Sie eine erstellen", - "wallet_group_description_three": "Sehen Sie den Bildschirm zur verfügbaren Brieftaschen und/oder Brieftaschengruppen. Oder wählen", - "wallet_group_description_two": "Durch die Auswahl einer vorhandenen Brieftasche, mit der ein Samen geteilt werden kann. Jede Brieftaschengruppe kann eine einzelne Brieftasche jedes Währungstyps enthalten. \n\n Sie können auswählen", - "wallet_group_empty_state_text_one": "Sieht so aus, als hätten Sie keine kompatiblen Brieftaschengruppen !\n\n TAP", + "wallet_group": "Walletgruppe", + "wallet_group_description_four": "eine Wallet mit einem völlig neuen Seed schaffen.", + "wallet_group_description_one": "In CakeWallet können Sie eine erstellen", + "wallet_group_description_three": "Sehen Sie den Bildschirm zur verfügbaren Wallet und/oder Walletgruppen. Oder wählen", + "wallet_group_description_two": "Durch die Auswahl einer vorhandenen Wallet, mit der ein Seed geteilt werden kann. Jede Walletgruppe kann eine einzelne Wallet jedes Währungstyps enthalten. \n\n Sie können auswählen", + "wallet_group_empty_state_text_one": "Sieht so aus, als hätten Sie keine kompatiblen Walletgruppen !\n\n TAP", "wallet_group_empty_state_text_two": "unten, um einen neuen zu machen.", "wallet_keys": "Wallet-Seed/-Schlüssel", "wallet_list_create_new_wallet": "Neue Wallet erstellen", @@ -901,7 +901,7 @@ "wallet_menu": "Wallet-Menü", "wallet_name": "Walletname", "wallet_name_exists": "Wallet mit diesem Namen existiert bereits", - "wallet_password_is_empty": "Brieftaschenkennwort ist leer. Brieftaschenkennwort sollte nicht leer sein", + "wallet_password_is_empty": "Walletkennwort ist leer. Walletkennwort sollte nicht leer sein", "wallet_recovery_height": "Erstellungshöhe", "wallet_restoration_store_incorrect_seed_length": "Falsche Seed-Länge", "wallet_seed": "Wallet-Seed", @@ -941,4 +941,4 @@ "you_will_get": "Konvertieren zu", "you_will_send": "Konvertieren von", "yy": "YY" -} \ No newline at end of file +} diff --git a/tool/configure.dart b/tool/configure.dart index 704b47526..97541c2fa 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -97,7 +97,7 @@ import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bip39/bip39.dart' as bip39; """; @@ -121,6 +121,7 @@ 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:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; @@ -223,8 +224,9 @@ abstract class Bitcoin { void deleteSilentPaymentAddress(Object wallet, String address); Future<void> updateFeeRates(Object wallet); int getMaxCustomFeeRate(Object wallet); - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); - Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); + Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs); bool txIsReceivedSilentPayment(TransactionInfo txInfo); bool txIsMweb(TransactionInfo txInfo); @@ -819,7 +821,7 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:web3dart/web3dart.dart'; """; @@ -885,7 +887,7 @@ abstract class Ethereum { Web3Client? getWeb3Client(WalletBase wallet); String getTokenAddress(CryptoCurrency asset); - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); } """; @@ -923,7 +925,7 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:web3dart/web3dart.dart'; """; @@ -989,7 +991,7 @@ abstract class Polygon { Web3Client? getWeb3Client(WalletBase wallet); String getTokenAddress(CryptoCurrency asset); - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); } """; diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c6444e09c..ad540a359 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h> #include <share_plus/share_plus_windows_plugin_c_api.h> +#include <universal_ble/universal_ble_plugin_c_api.h> #include <url_launcher_windows/url_launcher_windows.h> void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UniversalBlePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UniversalBlePluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 0a0b2f9eb..92431a6fb 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows permission_handler_windows share_plus + universal_ble url_launcher_windows )