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

Konstantin Ullrich committed Oct 23, 2024 at 17:38 UTC 68926c0a337700165b807e76f34a31c52a9d0b79
45 files changed +840 -403
assets/images/hardware_wallet/ledger_flex.png
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_flex.png differ
assets/images/hardware_wallet/ledger_nano_s.png
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_nano_s.png differ
assets/images/hardware_wallet/ledger_nano_x.png
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_nano_x.png differ
assets/images/hardware_wallet/ledger_stax.png
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_stax.png differ
assets/images/ledger_nano.png
Binary files a/assets/images/ledger_nano.png and /dev/null differ
cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
+11 -10
@@ -5,30 +5,31 @@ import 'package:blockchain_utils/blockchain_utils.dart';
5 import 'package:cw_bitcoin/utils.dart';
6 import 'package:cw_core/hardware/hardware_account_data.dart';
7 import 'package:ledger_bitcoin/ledger_bitcoin.dart';
8 -import 'package:ledger_flutter/ledger_flutter.dart';
8 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
9
10 class BitcoinHardwareWalletService {
11 - BitcoinHardwareWalletService(this.ledger, this.device);
11 + BitcoinHardwareWalletService(this.ledgerConnection);
12
13 - final Ledger ledger;
14 - final LedgerDevice device;
13 + final LedgerConnection ledgerConnection;
14
16 - Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
17 - final bitcoinLedgerApp = BitcoinLedgerApp(ledger);
15 + Future<List<HardwareAccountData>> getAvailableAccounts(
16 + {int index = 0, int limit = 5}) async {
17 + final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
18
19 - final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device);
20 - print(masterFp);
19 + final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
20
21 final accounts = <HardwareAccountData>[];
22 final indexRange = List.generate(limit, (i) => i + index);
23
24 for (final i in indexRange) {
25 final derivationPath = "m/84'/0'/$i'";
27 - final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
26 + final xpub =
27 + await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
28 Bip32Slip10Secp256k1 hd =
29 Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
30
31 - final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
31 + final address = generateP2WPKHAddress(
32 + hd: hd, index: 0, network: BitcoinNetwork.mainnet);
33
34 accounts.add(HardwareAccountData(
35 address: address,
cw_bitcoin/lib/bitcoin_wallet.dart
+33 -24
@@ -5,13 +5,13 @@ import 'package:bitcoin_base/bitcoin_base.dart';
5 import 'package:blockchain_utils/blockchain_utils.dart';
6 import 'package:cw_bitcoin/bitcoin_address_record.dart';
7 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
8 +import 'package:cw_bitcoin/psbt_transaction_builder.dart';
9 import 'package:cw_core/encryption_file_utils.dart';
10 import 'package:cw_bitcoin/electrum_derivations.dart';
11 import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
12 import 'package:cw_bitcoin/electrum_balance.dart';
13 import 'package:cw_bitcoin/electrum_wallet.dart';
14 import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
14 -import 'package:cw_bitcoin/psbt_transaction_builder.dart';
15 import 'package:cw_core/crypto_currency.dart';
16 import 'package:cw_core/unspent_coins_info.dart';
17 import 'package:cw_core/wallet_info.dart';
@@ -19,7 +19,7 @@ import 'package:cw_core/wallet_keys_file.dart';
19 import 'package:flutter/foundation.dart';
20 import 'package:hive/hive.dart';
21 import 'package:ledger_bitcoin/ledger_bitcoin.dart';
22 -import 'package:ledger_flutter/ledger_flutter.dart';
22 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
23 import 'package:mobx/mobx.dart';
24
25 part 'bitcoin_wallet.g.dart';
@@ -61,8 +61,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
61 initialBalance: initialBalance,
62 seedBytes: seedBytes,
63 encryptionFileUtils: encryptionFileUtils,
64 - currency:
65 - networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
64 + currency: networkParam == BitcoinNetwork.testnet
65 + ? CryptoCurrency.tbtc
66 + : CryptoCurrency.btc,
67 alwaysScan: alwaysScan,
68 ) {
69 // 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 {
81 mainHd: hd,
82 sideHd: accountHD.childKey(Bip32KeyIndex(1)),
83 network: networkParam ?? network,
83 - masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
84 + masterHd:
85 + seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
86 + isHardwareWallet: walletInfo.isHardwareWallet,
87 );
88
89 autorun((_) {
87 - this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
90 + this.walletAddresses.isEnabledAutoGenerateSubaddress =
91 + this.isEnabledAutoGenerateSubaddress;
92 });
93 }
94
@@ -185,8 +189,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
189 walletInfo.derivationInfo ??= DerivationInfo();
190
191 // set the default if not present:
188 - walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
189 - walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
192 + walletInfo.derivationInfo!.derivationPath ??=
193 + snp?.derivationPath ?? electrum_path;
194 + walletInfo.derivationInfo!.derivationType ??=
195 + snp?.derivationType ?? DerivationType.electrum;
196
197 Uint8List? seedBytes = null;
198 final mnemonic = keysData.mnemonic;
@@ -228,15 +234,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
234 );
235 }
236
231 - Ledger? _ledger;
232 - LedgerDevice? _ledgerDevice;
237 + LedgerConnection? _ledgerConnection;
238 BitcoinLedgerApp? _bitcoinLedgerApp;
239
235 - void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
236 - _ledger = setLedger;
237 - _ledgerDevice = setLedgerDevice;
238 - _bitcoinLedgerApp =
239 - BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
240 + @override
241 + void setLedgerConnection(LedgerConnection connection) {
242 + _ledgerConnection = connection;
243 + _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!,
244 + derivationPath: walletInfo.derivationInfo!.derivationPath!);
245 }
246
247 @override
@@ -251,12 +256,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
256 BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
257 BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
258 }) async {
254 - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!);
259 + final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
260
261 final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
262 for (final utxo in utxos) {
258 - final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
259 - final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
263 + final rawTx =
264 + await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
265 + final publicKeyAndDerivationPath =
266 + publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
267
268 psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
269 utxo: utxo.utxo,
@@ -268,10 +275,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
275 ));
276 }
277
271 - final psbt =
272 - PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
278 + final psbt = PSBTTransactionBuild(
279 + inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
280
274 - final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
281 + final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt);
282 return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
283 }
284
@@ -279,14 +286,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
286 Future<String> signMessage(String message, {String? address = null}) async {
287 if (walletInfo.isHardwareWallet) {
288 final addressEntry = address != null
282 - ? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
289 + ? walletAddresses.allAddresses
290 + .firstWhere((element) => element.address == address)
291 : null;
292 final index = addressEntry?.index ?? 0;
293 final isChange = addressEntry?.isHidden == true ? 1 : 0;
294 final accountPath = walletInfo.derivationInfo?.derivationPath;
287 - final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
295 + final derivationPath =
296 + accountPath != null ? "$accountPath/$isChange/$index" : null;
297
289 - final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
298 + final signature = await _bitcoinLedgerApp!.signMessage(
299 message: ascii.encode(message), signDerivationPath: derivationPath);
300 return base64Encode(signature);
301 }
cw_bitcoin/lib/bitcoin_wallet_addresses.dart
+1
@@ -15,6 +15,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
15 required super.mainHd,
16 required super.sideHd,
17 required super.network,
18 + required super.isHardwareWallet,
19 super.initialAddresses,
20 super.initialRegularAddressIndex,
21 super.initialChangeAddressIndex,
cw_bitcoin/lib/electrum_wallet.dart
+51 -18
@@ -6,7 +6,6 @@ import 'dart:isolate';
6 import 'package:bitcoin_base/bitcoin_base.dart';
7 import 'package:cw_bitcoin/bitcoin_wallet.dart';
8 import 'package:shared_preferences/shared_preferences.dart';
9 -import 'package:cw_core/encryption_file_utils.dart';
9 import 'package:blockchain_utils/blockchain_utils.dart';
10 import 'package:collection/collection.dart';
11 import 'package:cw_bitcoin/address_from_output.dart';
@@ -26,6 +25,8 @@ import 'package:cw_bitcoin/exceptions.dart';
25 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
26 import 'package:cw_bitcoin/utils.dart';
27 import 'package:cw_core/crypto_currency.dart';
28 +import 'package:cw_core/encryption_file_utils.dart';
29 +import 'package:cw_core/get_height_by_date.dart';
30 import 'package:cw_core/node.dart';
31 import 'package:cw_core/pathForWallet.dart';
32 import 'package:cw_core/pending_transaction.dart';
@@ -37,10 +38,10 @@ import 'package:cw_core/wallet_base.dart';
38 import 'package:cw_core/wallet_info.dart';
39 import 'package:cw_core/wallet_keys_file.dart';
40 import 'package:cw_core/wallet_type.dart';
40 -import 'package:cw_core/get_height_by_date.dart';
41 import 'package:cw_core/unspent_coin_type.dart';
42 import 'package:flutter/foundation.dart';
43 import 'package:hive/hive.dart';
44 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
45 import 'package:mobx/mobx.dart';
46 import 'package:rxdart/subjects.dart';
47 import 'package:sp_scanner/sp_scanner.dart';
@@ -51,9 +52,10 @@ part 'electrum_wallet.g.dart';
52
53 class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
54
54 -abstract class ElectrumWalletBase
55 - extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
56 - with Store, WalletKeysFile {
55 +abstract class ElectrumWalletBase extends WalletBase<
56 + ElectrumBalance,
57 + ElectrumTransactionHistory,
58 + ElectrumTransactionInfo> with Store, WalletKeysFile {
59 ElectrumWalletBase({
60 required String password,
61 required WalletInfo walletInfo,
@@ -69,8 +71,8 @@ abstract class ElectrumWalletBase
71 ElectrumBalance? initialBalance,
72 CryptoCurrency? currency,
73 this.alwaysScan,
72 - }) : accountHD =
73 - getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
74 + }) : accountHD = getAccountHDWallet(
75 + currency, network, seedBytes, xpub, walletInfo.derivationInfo),
76 syncStatus = NotConnectedSyncStatus(),
77 _password = password,
78 _feeRates = <int>[],
@@ -105,8 +107,12 @@ abstract class ElectrumWalletBase
107 sharedPrefs.complete(SharedPreferences.getInstance());
108 }
109
108 - static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
109 - Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
110 + static Bip32Slip10Secp256k1 getAccountHDWallet(
111 + CryptoCurrency? currency,
112 + BasedUtxoNetwork network,
113 + Uint8List? seedBytes,
114 + String? xpub,
115 + DerivationInfo? derivationInfo) {
116 if (seedBytes == null && xpub == null) {
117 throw Exception(
118 "To create a Wallet you need either a seed or an xpub. This should not happen");
@@ -117,8 +123,9 @@ abstract class ElectrumWalletBase
123 case CryptoCurrency.btc:
124 case CryptoCurrency.ltc:
125 case CryptoCurrency.tbtc:
120 - return Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
121 - _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
126 + return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network))
127 + .derivePath(_hardenedDerivationPath(
128 + derivationInfo?.derivationPath ?? electrum_path))
129 as Bip32Slip10Secp256k1;
130 case CryptoCurrency.bch:
131 return bitcoinCashHDWallet(seedBytes);
@@ -127,15 +134,26 @@ abstract class ElectrumWalletBase
134 }
135 }
136
130 - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!);
137 + return Bip32Slip10Secp256k1.fromExtendedKey(
138 + xpub!, getKeyNetVersion(network));
139 }
140
141 static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
134 - Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
142 + Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'")
143 + as Bip32Slip10Secp256k1;
144
145 static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
146 inputsCount * 68 + outputsCounts * 34 + 10;
147
148 + static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) {
149 + switch (network) {
150 + case LitecoinNetwork.mainnet:
151 + return Bip44Conf.litecoinMainNet.altKeyNetVer;
152 + default:
153 + return null;
154 + }
155 + }
156 +
157 bool? alwaysScan;
158
159 final Bip32Slip10Secp256k1 accountHD;
@@ -634,8 +652,9 @@ abstract class ElectrumWalletBase
652 ECPrivate? privkey;
653 bool? isSilentPayment = false;
654
637 - final hd =
638 - utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
655 + final hd = utx.bitcoinAddressRecord.isHidden
656 + ? walletAddresses.sideHd
657 + : walletAddresses.mainHd;
658
659 if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
660 final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
@@ -833,7 +852,7 @@ abstract class ElectrumWalletBase
852 inputs: utxoDetails.availableInputs,
853 outputs: updatedOutputs,
854 );
836 - final address = RegexUtils.addressTypeFromStr(changeAddress, network);
855 + final address = RegexUtils.addressTypeFromStr(changeAddress.address, network);
856 updatedOutputs.add(BitcoinOutput(
857 address: address,
858 value: BigInt.from(amountLeftForChangeAndFee),
@@ -845,6 +864,14 @@ abstract class ElectrumWalletBase
864 isChange: true,
865 ));
866
867 + // Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets
868 + final changeDerivationPath =
869 + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
870 + "/${changeAddress.isHidden ? "1" : "0"}"
871 + "/${changeAddress.index}";
872 + utxoDetails.publicKeys[address.pubKeyHash()] =
873 + PublicKeyWithDerivationPath('', changeDerivationPath);
874 +
875 // calcFee updates the silent payment outputs to calculate the tx size accounting
876 // for taproot addresses, but if more inputs are needed to make up for fees,
877 // the silent payment outputs need to be recalculated for the new inputs
@@ -1206,6 +1233,9 @@ abstract class ElectrumWalletBase
1233 }
1234 }
1235
1236 + void setLedgerConnection(ledger.LedgerConnection connection) =>
1237 + throw UnimplementedError();
1238 +
1239 Future<BtcTransaction> buildHardwareWalletTransaction({
1240 required List<BitcoinBaseOutput> outputs,
1241 required BigInt fee,
@@ -1563,7 +1593,9 @@ abstract class ElectrumWalletBase
1593
1594 final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network);
1595 final privkey = generateECPrivate(
1566 - hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
1596 + hd: addressRecord.isHidden
1597 + ? walletAddresses.sideHd
1598 + : walletAddresses.mainHd,
1599 index: addressRecord.index,
1600 network: network);
1601
@@ -1745,7 +1777,8 @@ abstract class ElectrumWalletBase
1777
1778 if (height != null) {
1779 if (time == null && height > 0) {
1748 - time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
1780 + time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000)
1781 + .round();
1782 }
1783
1784 if (confirmations == null) {
cw_bitcoin/lib/electrum_wallet_addresses.dart
+14 -7
@@ -36,6 +36,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
36 required this.mainHd,
37 required this.sideHd,
38 required this.network,
39 + required this.isHardwareWallet,
40 List<BitcoinAddressRecord>? initialAddresses,
41 Map<String, int>? initialRegularAddressIndex,
42 Map<String, int>? initialChangeAddressIndex,
@@ -44,6 +45,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
45 List<BitcoinAddressRecord>? initialMwebAddresses,
46 Bip32Slip10Secp256k1? masterHd,
47 BitcoinAddressType? initialAddressPageType,
48 +
49 }) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
50 addressesByReceiveType =
51 ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
@@ -112,6 +114,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
114 final BasedUtxoNetwork network;
115 final Bip32Slip10Secp256k1 mainHd;
116 final Bip32Slip10Secp256k1 sideHd;
117 + final bool isHardwareWallet;
118
119 @observable
120 SilentPaymentOwner? silentAddress;
@@ -240,15 +243,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
243 await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
244 } else if (walletInfo.type == WalletType.litecoin) {
245 await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
243 - if (Platform.isAndroid || Platform.isIOS) {
246 + if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
247 await _generateInitialAddresses(type: SegwitAddresType.mweb);
248 }
249 } else if (walletInfo.type == WalletType.bitcoin) {
250 await _generateInitialAddresses();
248 - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
249 - await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
250 - await _generateInitialAddresses(type: SegwitAddresType.p2tr);
251 - await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
251 + if (!isHardwareWallet) {
252 + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
253 + await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
254 + await _generateInitialAddresses(type: SegwitAddresType.p2tr);
255 + await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
256 + }
257 }
258
259 updateAddressesByMatch();
@@ -267,7 +272,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
272 }
273
274 @action
270 - Future<String> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
275 + Future<BitcoinAddressRecord> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
276 updateChangeAddresses();
277
278 if (changeAddresses.isEmpty) {
@@ -282,7 +287,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
287 }
288
289 updateChangeAddresses();
285 - final address = changeAddresses[currentChangeAddressIndex].address;
290 + final address = changeAddresses[currentChangeAddressIndex];
291 currentChangeAddressIndex += 1;
292 return address;
293 }
@@ -670,7 +675,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
675 }
676
677 Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
678 +
679 bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
680 +
681 bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
682 !addr.isHidden && !addr.isUsed && addr.type == type;
683
cw_bitcoin/lib/litecoin_hardware_wallet_service.dart new
+46
@@ -0,0 +1,46 @@
1 +import 'dart:async';
2 +
3 +import 'package:bitcoin_base/bitcoin_base.dart';
4 +import 'package:blockchain_utils/blockchain_utils.dart';
5 +import 'package:cw_bitcoin/utils.dart';
6 +import 'package:cw_core/hardware/hardware_account_data.dart';
7 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
8 +import 'package:ledger_litecoin/ledger_litecoin.dart';
9 +
10 +class LitecoinHardwareWalletService {
11 + LitecoinHardwareWalletService(this.ledgerConnection);
12 +
13 + final LedgerConnection ledgerConnection;
14 +
15 + Future<List<HardwareAccountData>> getAvailableAccounts(
16 + {int index = 0, int limit = 5}) async {
17 + final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
18 +
19 + await litecoinLedgerApp.getVersion();
20 +
21 + final accounts = <HardwareAccountData>[];
22 + final indexRange = List.generate(limit, (i) => i + index);
23 + final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
24 +
25 + for (final i in indexRange) {
26 + final derivationPath = "m/84'/2'/$i'";
27 + final xpub = await litecoinLedgerApp.getXPubKey(
28 + accountsDerivationPath: derivationPath,
29 + xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
30 + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
31 + .childKey(Bip32KeyIndex(0));
32 +
33 + final address = generateP2WPKHAddress(
34 + hd: hd, index: 0, network: LitecoinNetwork.mainnet);
35 +
36 + accounts.add(HardwareAccountData(
37 + address: address,
38 + accountIndex: i,
39 + derivationPath: derivationPath,
40 + xpub: xpub,
41 + ));
42 + }
43 +
44 + return accounts;
45 + }
46 +}
cw_bitcoin/lib/litecoin_wallet.dart
+90 -12
@@ -1,5 +1,7 @@
1 import 'dart:async';
2 import 'dart:convert';
3 +import 'dart:typed_data';
4 +
5 import 'package:convert/convert.dart' as convert;
6 import 'dart:math';
7 import 'package:collection/collection.dart';
@@ -37,6 +39,8 @@ import 'package:cw_core/wallet_keys_file.dart';
39 import 'package:flutter/foundation.dart';
40 import 'package:grpc/grpc.dart';
41 import 'package:hive/hive.dart';
42 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
43 +import 'package:ledger_litecoin/ledger_litecoin.dart';
44 import 'package:mobx/mobx.dart';
45 import 'package:cw_core/wallet_type.dart';
46 import 'package:cw_mweb/cw_mweb.dart';
@@ -50,12 +54,13 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
54
55 abstract class LitecoinWalletBase extends ElectrumWallet with Store {
56 LitecoinWalletBase({
53 - required String mnemonic,
57 required String password,
58 required WalletInfo walletInfo,
59 required Box<UnspentCoinsInfo> unspentCoinsInfo,
57 - required Uint8List seedBytes,
60 required EncryptionFileUtils encryptionFileUtils,
61 + Uint8List? seedBytes,
62 + String? mnemonic,
63 + String? xpub,
64 String? passphrase,
65 String? addressPageType,
66 List<BitcoinAddressRecord>? initialAddresses,
@@ -68,6 +73,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
73 }) : super(
74 mnemonic: mnemonic,
75 password: password,
76 + xpub: xpub,
77 walletInfo: walletInfo,
78 unspentCoinsInfo: unspentCoinsInfo,
79 network: LitecoinNetwork.mainnet,
@@ -78,8 +84,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
84 currency: CryptoCurrency.ltc,
85 alwaysScan: alwaysScan,
86 ) {
81 - mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
82 - mwebEnabled = alwaysScan ?? false;
87 + if (seedBytes != null) {
88 + mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
89 + "m/1000'") as Bip32Slip10Secp256k1;
90 + mwebEnabled = alwaysScan ?? false;
91 + } else {
92 + mwebHd = null;
93 + mwebEnabled = false;
94 + }
95 walletAddresses = LitecoinWalletAddresses(
96 walletInfo,
97 initialAddresses: initialAddresses,
@@ -91,6 +103,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
103 network: network,
104 mwebHd: mwebHd,
105 mwebEnabled: mwebEnabled,
106 + isHardwareWallet: walletInfo.isHardwareWallet,
107 );
108 autorun((_) {
109 this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
@@ -126,7 +139,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
139 }
140 });
141 }
129 - late final Bip32Slip10Secp256k1 mwebHd;
142 + late final Bip32Slip10Secp256k1? mwebHd;
143 late final Box<MwebUtxo> mwebUtxosBox;
144 Timer? _syncTimer;
145 Timer? _feeRatesTimer;
@@ -138,8 +151,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
151 @observable
152 SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
153
141 - List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
142 - List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
154 + List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
155 + List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
156
157 static Future<LitecoinWallet> create(
158 {required String mnemonic,
@@ -249,14 +262,15 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
262 }
263
264 return LitecoinWallet(
252 - mnemonic: keysData.mnemonic!,
265 + mnemonic: keysData.mnemonic,
266 + xpub: keysData.xPub,
267 password: password,
268 walletInfo: walletInfo,
269 unspentCoinsInfo: unspentCoinsInfo,
270 initialAddresses: snp?.addresses,
271 initialMwebAddresses: snp?.mwebAddresses,
272 initialBalance: snp?.balance,
259 - seedBytes: seedBytes!,
273 + seedBytes: seedBytes,
274 passphrase: passphrase,
275 encryptionFileUtils: encryptionFileUtils,
276 initialRegularAddressIndex: snp?.regularAddressIndex,
@@ -935,7 +949,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
949
950 if (!mwebEnabled) {
951 tx.changeAddressOverride =
938 - await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false);
952 + (await (walletAddresses as LitecoinWalletAddresses)
953 + .getChangeAddress(isPegIn: false))
954 + .address;
955 return tx;
956 }
957 await waitForMwebAddresses();
@@ -974,8 +990,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
990
991 bool isPegIn = !hasMwebInput && hasMwebOutput;
992 bool isRegular = !hasMwebInput && !hasMwebOutput;
977 - tx.changeAddressOverride = await (walletAddresses as LitecoinWalletAddresses)
978 - .getChangeAddress(isPegIn: isPegIn || isRegular);
993 + tx.changeAddressOverride =
994 + (await (walletAddresses as LitecoinWalletAddresses)
995 + .getChangeAddress(isPegIn: isPegIn || isRegular))
996 + .address;
997 if (!hasMwebInput && !hasMwebOutput) {
998 tx.isMweb = false;
999 return tx;
@@ -1215,4 +1233,64 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
1233
1234 return false;
1235 }
1236 +
1237 + LedgerConnection? _ledgerConnection;
1238 + LitecoinLedgerApp? _litecoinLedgerApp;
1239 +
1240 + @override
1241 + void setLedgerConnection(LedgerConnection connection) {
1242 + _ledgerConnection = connection;
1243 + _litecoinLedgerApp =
1244 + LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
1245 + }
1246 +
1247 + @override
1248 + Future<BtcTransaction> buildHardwareWalletTransaction({
1249 + required List<BitcoinBaseOutput> outputs,
1250 + required BigInt fee,
1251 + required BasedUtxoNetwork network,
1252 + required List<UtxoWithAddress> utxos,
1253 + required Map<String, PublicKeyWithDerivationPath> publicKeys,
1254 + String? memo,
1255 + bool enableRBF = false,
1256 + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
1257 + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
1258 + }) async {
1259 + final readyInputs = <LedgerTransaction>[];
1260 + for (final utxo in utxos) {
1261 + final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
1262 + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
1263 +
1264 + readyInputs.add(LedgerTransaction(
1265 + rawTx: rawTx,
1266 + outputIndex: utxo.utxo.vout,
1267 + ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
1268 + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
1269 + // sequence: enableRBF ? 0x1 : 0xffffffff,
1270 + sequence: 0xffffffff,
1271 + ));
1272 + }
1273 +
1274 + String? changePath;
1275 + for (final output in outputs) {
1276 + final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
1277 + if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
1278 + }
1279 +
1280 +
1281 + final rawHex = await _litecoinLedgerApp!.createTransaction(
1282 + inputs: readyInputs,
1283 + outputs: outputs
1284 + .map((e) => TransactionOutput.fromBigInt(
1285 + (e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
1286 + .toList(),
1287 + changePath: changePath,
1288 + sigHashType: 0x01,
1289 + additionals: ["bech32"],
1290 + isSegWit: true,
1291 + useTrustedInputForSegwit: true
1292 + );
1293 +
1294 + return BtcTransaction.fromRaw(rawHex);
1295 + }
1296 }
cw_bitcoin/lib/litecoin_wallet_addresses.dart
+20 -9
@@ -16,14 +16,17 @@ import 'package:mobx/mobx.dart';
16
17 part 'litecoin_wallet_addresses.g.dart';
18
19 -class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
19 +class LitecoinWalletAddresses = LitecoinWalletAddressesBase
20 + with _$LitecoinWalletAddresses;
21
21 -abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
22 +abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
23 + with Store {
24 LitecoinWalletAddressesBase(
25 WalletInfo walletInfo, {
26 required super.mainHd,
27 required super.sideHd,
28 required super.network,
29 + required super.isHardwareWallet,
30 required this.mwebHd,
31 required this.mwebEnabled,
32 super.initialAddresses,
@@ -37,20 +40,20 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
40 print("initialized with ${mwebAddrs.length} mweb addresses");
41 }
42
40 - final Bip32Slip10Secp256k1 mwebHd;
43 + final Bip32Slip10Secp256k1? mwebHd;
44 bool mwebEnabled;
45 int mwebTopUpIndex = 1000;
46 List<String> mwebAddrs = [];
47 bool generating = false;
48
46 - List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
47 -
49 + List<int> get scanSecret =>
50 + mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
51 List<int> get spendPubkey =>
49 - mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
52 + mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
53
54 @override
55 Future<void> init() async {
53 - await initMwebAddresses();
56 + if (!isHardwareWallet) await initMwebAddresses();
57 await super.init();
58 }
59
@@ -94,6 +97,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
97 print("Done generating MWEB addresses len: ${mwebAddrs.length}");
98
99 // ensure mweb addresses are up to date:
100 + // This is the Case if the Litecoin Wallet is a hardware Wallet
101 + if (mwebHd == null) return;
102 +
103 if (mwebAddresses.length < mwebAddrs.length) {
104 List<BitcoinAddressRecord> addressRecords = mwebAddrs
105 .asMap()
@@ -143,7 +149,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
149
150 @action
151 @override
146 - Future<String> getChangeAddress(
152 + Future<BitcoinAddressRecord> getChangeAddress(
153 {List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
154 // use regular change address on peg in, otherwise use mweb for change address:
155
@@ -187,7 +193,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
193
194 if (mwebEnabled) {
195 await ensureMwebAddressUpToIndexExists(1);
190 - return mwebAddrs[0];
196 + return BitcoinAddressRecord(
197 + mwebAddrs[0],
198 + index: 0,
199 + type: SegwitAddresType.mweb,
200 + network: network,
201 + );
202 }
203
204 return super.getChangeAddress();
cw_bitcoin/lib/litecoin_wallet_service.dart
+19 -4
@@ -1,4 +1,5 @@
1 import 'dart:io';
2 +import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
4 import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
5 import 'package:cw_core/encryption_file_utils.dart';
@@ -20,7 +21,7 @@ class LitecoinWalletService extends WalletService<
21 BitcoinNewWalletCredentials,
22 BitcoinRestoreWalletFromSeedCredentials,
23 BitcoinRestoreWalletFromWIFCredentials,
23 - BitcoinNewWalletCredentials> {
24 + BitcoinRestoreWalletFromHardware> {
25 LitecoinWalletService(
26 this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
27
@@ -147,9 +148,23 @@ class LitecoinWalletService extends WalletService<
148 }
149
150 @override
150 - Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
151 - throw UnimplementedError(
152 - "Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
151 + Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
152 + {bool? isTestnet}) async {
153 + final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet;
154 + credentials.walletInfo?.network = network.value;
155 + credentials.walletInfo?.derivationInfo?.derivationPath =
156 + credentials.hwAccountData.derivationPath;
157 +
158 + final wallet = await LitecoinWallet(
159 + password: credentials.password!,
160 + xpub: credentials.hwAccountData.xpub,
161 + walletInfo: credentials.walletInfo!,
162 + unspentCoinsInfo: unspentCoinsInfoSource,
163 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
164 + );
165 + await wallet.save();
166 + await wallet.init();
167 + return wallet;
168 }
169
170 @override
cw_bitcoin/lib/psbt_transaction_builder.dart
-4
@@ -16,10 +16,6 @@ class PSBTTransactionBuild {
16 for (var i = 0; i < inputs.length; i++) {
17 final input = inputs[i];
18
19 - print(input.utxo.isP2tr());
20 - print(input.utxo.isSegwit());
21 - print(input.utxo.isP2shSegwit());
22 -
19 psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
20 psbt.setInputOutputIndex(i, input.utxo.vout);
21 psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);
cw_bitcoin/pubspec.lock
+80 -48
@@ -101,6 +101,14 @@ packages:
101 url: "https://github.com/cake-tech/blockchain_utils"
102 source: git
103 version: "3.3.0"
104 + bluez:
105 + dependency: transitive
106 + description:
107 + name: bluez
108 + sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
109 + url: "https://pub.dev"
110 + source: hosted
111 + version: "0.8.2"
112 boolean_selector:
113 dependency: transitive
114 description:
@@ -300,6 +308,14 @@ packages:
308 url: "https://pub.dev"
309 source: hosted
310 version: "1.0.3"
311 + dbus:
312 + dependency: transitive
313 + description:
314 + name: dbus
315 + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
316 + url: "https://pub.dev"
317 + source: hosted
318 + version: "0.7.10"
319 encrypt:
320 dependency: transitive
321 description:
@@ -361,19 +377,19 @@ packages:
377 url: "https://pub.dev"
378 source: hosted
379 version: "2.2.1+1"
364 - flutter_reactive_ble:
365 - dependency: transitive
366 - description:
367 - name: flutter_reactive_ble
368 - sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
369 - url: "https://pub.dev"
370 - source: hosted
371 - version: "5.3.1"
380 flutter_test:
381 dependency: "direct dev"
382 description: flutter
383 source: sdk
384 version: "0.0.0"
385 + flutter_web_bluetooth:
386 + dependency: transitive
387 + description:
388 + name: flutter_web_bluetooth
389 + sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
390 + url: "https://pub.dev"
391 + source: hosted
392 + version: "0.2.3"
393 flutter_web_plugins:
394 dependency: transitive
395 description: flutter
@@ -387,14 +403,6 @@ packages:
403 url: "https://pub.dev"
404 source: hosted
405 version: "4.0.0"
390 - functional_data:
391 - dependency: transitive
392 - description:
393 - name: functional_data
394 - sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
395 - url: "https://pub.dev"
396 - source: hosted
397 - version: "1.2.0"
406 glob:
407 dependency: transitive
408 description:
@@ -550,29 +558,37 @@ packages:
558 ledger_bitcoin:
559 dependency: "direct main"
560 description:
553 - path: "."
561 + path: "packages/ledger-bitcoin"
562 ref: HEAD
555 - resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab
556 - url: "https://github.com/cake-tech/ledger-bitcoin"
563 + resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
564 + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
565 source: git
558 - version: "0.0.2"
559 - ledger_flutter:
566 + version: "0.0.3"
567 + ledger_flutter_plus:
568 dependency: "direct main"
569 description:
562 - path: "."
563 - ref: cake-v3
564 - resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4"
565 - url: "https://github.com/cake-tech/ledger-flutter.git"
570 + name: ledger_flutter_plus
571 + sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
572 + url: "https://pub.dev"
573 + source: hosted
574 + version: "1.2.5"
575 + ledger_litecoin:
576 + dependency: "direct main"
577 + description:
578 + path: "packages/ledger-litecoin"
579 + ref: HEAD
580 + resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
581 + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
582 source: git
567 - version: "1.0.2"
568 - ledger_usb:
583 + version: "0.0.2"
584 + ledger_usb_plus:
585 dependency: transitive
586 description:
571 - name: ledger_usb
572 - sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2"
587 + name: ledger_usb_plus
588 + sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
589 url: "https://pub.dev"
590 source: hosted
575 - version: "1.0.0"
591 + version: "1.0.4"
592 logging:
593 dependency: transitive
594 description:
@@ -701,6 +717,14 @@ packages:
717 url: "https://pub.dev"
718 source: hosted
719 version: "2.3.0"
720 + petitparser:
721 + dependency: transitive
722 + description:
723 + name: petitparser
724 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
725 + url: "https://pub.dev"
726 + source: hosted
727 + version: "6.0.2"
728 platform:
729 dependency: transitive
730 description:
@@ -773,30 +797,14 @@ packages:
797 url: "https://pub.dev"
798 source: hosted
799 version: "3.2.2"
776 - reactive_ble_mobile:
777 - dependency: transitive
778 - description:
779 - name: reactive_ble_mobile
780 - sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
781 - url: "https://pub.dev"
782 - source: hosted
783 - version: "5.3.1"
784 - reactive_ble_platform_interface:
785 - dependency: transitive
786 - description:
787 - name: reactive_ble_platform_interface
788 - sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
789 - url: "https://pub.dev"
790 - source: hosted
791 - version: "5.3.1"
800 rxdart:
801 dependency: "direct main"
802 description:
803 name: rxdart
796 - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
804 + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
805 url: "https://pub.dev"
806 source: hosted
799 - version: "0.27.7"
807 + version: "0.28.0"
808 shared_preferences:
809 dependency: "direct main"
810 description:
@@ -987,6 +995,22 @@ packages:
995 url: "https://pub.dev"
996 source: hosted
997 version: "1.3.2"
998 + universal_ble:
999 + dependency: transitive
1000 + description:
1001 + name: universal_ble
1002 + sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
1003 + url: "https://pub.dev"
1004 + source: hosted
1005 + version: "0.12.0"
1006 + universal_platform:
1007 + dependency: transitive
1008 + description:
1009 + name: universal_platform
1010 + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
1011 + url: "https://pub.dev"
1012 + source: hosted
1013 + version: "1.1.0"
1014 unorm_dart:
1015 dependency: transitive
1016 description:
@@ -1043,6 +1067,14 @@ packages:
1067 url: "https://pub.dev"
1068 source: hosted
1069 version: "1.1.0"
1070 + xml:
1071 + dependency: transitive
1072 + description:
1073 + name: xml
1074 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
1075 + url: "https://pub.dev"
1076 + source: hosted
1077 + version: "6.5.0"
1078 yaml:
1079 dependency: transitive
1080 description:
cw_bitcoin/pubspec.yaml
+10 -9
@@ -24,16 +24,12 @@ dependencies:
24 git:
25 url: https://github.com/cake-tech/bitbox-flutter.git
26 ref: Add-Support-For-OP-Return-data
27 - rxdart: ^0.27.5
27 + rxdart: ^0.28.0
28 cryptography: ^2.0.5
29 blockchain_utils:
30 git:
31 url: https://github.com/cake-tech/blockchain_utils
32 ref: cake-update-v2
33 - ledger_flutter: ^1.0.1
34 - ledger_bitcoin:
35 - git:
36 - url: https://github.com/cake-tech/ledger-bitcoin
33 cw_mweb:
34 path: ../cw_mweb
35 grpc: ^3.2.4
@@ -44,6 +40,15 @@ dependencies:
40 bech32:
41 git:
42 url: https://github.com/cake-tech/bech32.git
43 + ledger_flutter_plus: ^1.4.1
44 + ledger_bitcoin:
45 + git:
46 + url: https://github.com/cake-tech/ledger-flutter-plus-plugins
47 + path: packages/ledger-bitcoin
48 + ledger_litecoin:
49 + git:
50 + url: https://github.com/cake-tech/ledger-flutter-plus-plugins
51 + path: packages/ledger-litecoin
52
53 dev_dependencies:
54 flutter_test:
@@ -54,10 +59,6 @@ dev_dependencies:
59 hive_generator: ^1.1.3
60
61 dependency_overrides:
57 - ledger_flutter:
58 - git:
59 - url: https://github.com/cake-tech/ledger-flutter.git
60 - ref: cake-v3
62 watcher: ^1.1.0
63 protobuf: ^3.1.0
64 bitcoin_base:
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+1
@@ -58,6 +58,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
58 sideHd: accountHD.childKey(Bip32KeyIndex(1)),
59 network: network,
60 initialAddressPageType: addressPageType,
61 + isHardwareWallet: walletInfo.isHardwareWallet,
62 );
63 autorun((_) {
64 this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart
+1
@@ -15,6 +15,7 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
15 required super.mainHd,
16 required super.sideHd,
17 required super.network,
18 + required super.isHardwareWallet,
19 super.initialAddresses,
20 super.initialRegularAddressIndex,
21 super.initialChangeAddressIndex,
cw_core/lib/hardware/device_connection_type.dart
+1
@@ -8,6 +8,7 @@ enum DeviceConnectionType {
8 [bool isIOS = false]) {
9 switch (walletType) {
10 case WalletType.bitcoin:
11 + case WalletType.litecoin:
12 case WalletType.ethereum:
13 case WalletType.polygon:
14 if (isIOS) return [DeviceConnectionType.ble];
cw_evm/lib/evm_chain_hardware_wallet_service.dart
+9 -9
@@ -2,26 +2,26 @@ import 'dart:async';
2
3 import 'package:cw_core/hardware/hardware_account_data.dart';
4 import 'package:ledger_ethereum/ledger_ethereum.dart';
5 -import 'package:ledger_flutter/ledger_flutter.dart';
5 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
6
7 class EVMChainHardwareWalletService {
8 - EVMChainHardwareWalletService(this.ledger, this.device);
8 + EVMChainHardwareWalletService(this.ledgerConnection);
9
10 - final Ledger ledger;
11 - final LedgerDevice device;
10 + final LedgerConnection ledgerConnection;
11
13 - Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
14 - final ethereumLedgerApp = EthereumLedgerApp(ledger);
12 + Future<List<HardwareAccountData>> getAvailableAccounts(
13 + {int index = 0, int limit = 5}) async {
14 + final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection);
15
16 - final version = await ethereumLedgerApp.getVersion(device);
16 + await ethereumLedgerApp.getVersion();
17
18 final accounts = <HardwareAccountData>[];
19 final indexRange = List.generate(limit, (i) => i + index);
20
21 for (final i in indexRange) {
22 final derivationPath = "m/44'/60'/$i'/0/0";
23 - final address =
24 - await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath);
23 + final address = await ethereumLedgerApp.getAccounts(
24 + accountsDerivationPath: derivationPath);
25
26 accounts.add(HardwareAccountData(
27 address: address.first,
cw_evm/lib/evm_chain_transaction_credentials.dart
-1
@@ -1,7 +1,6 @@
1 import 'package:cw_core/crypto_currency.dart';
2 import 'package:cw_core/output_info.dart';
3 import 'package:cw_evm/evm_chain_transaction_priority.dart';
4 -import 'package:ledger_flutter/ledger_flutter.dart';
4
5 class EVMChainTransactionCredentials {
6 EVMChainTransactionCredentials(
cw_evm/lib/evm_ledger_credentials.dart
+30 -27
@@ -1,17 +1,16 @@
1 import 'dart:async';
2 import 'dart:typed_data';
3
4 -import 'package:cw_core/hardware/device_not_connected_exception.dart';
4 +import 'package:cw_core/hardware/device_not_connected_exception.dart'
5 + as exception;
6 import 'package:ledger_ethereum/ledger_ethereum.dart';
6 -import 'package:ledger_flutter/ledger_flutter.dart';
7 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
8 import 'package:web3dart/crypto.dart';
9 import 'package:web3dart/web3dart.dart';
10
11 class EvmLedgerCredentials extends CredentialsWithKnownAddress {
12 final String _address;
13
13 - Ledger? ledger;
14 - LedgerDevice? ledgerDevice;
14 EthereumLedgerApp? ethereumLedgerApp;
15
16 EvmLedgerCredentials(this._address);
@@ -19,25 +18,25 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
18 @override
19 EthereumAddress get address => EthereumAddress.fromHex(_address);
20
22 - void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) {
23 - ledger = setLedger;
24 - ledgerDevice = setLedgerDevice;
25 - ethereumLedgerApp =
26 - EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
21 + void setLedgerConnection(LedgerConnection connection,
22 + [String? derivationPath]) {
23 + ethereumLedgerApp = EthereumLedgerApp(connection,
24 + derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
25 }
26
27 @override
30 - MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
31 - throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
28 + MsgSignature signToEcSignature(Uint8List payload,
29 + {int? chainId, bool isEIP1559 = false}) =>
30 + throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
31
32 @override
33 Future<MsgSignature> signToSignature(Uint8List payload,
34 {int? chainId, bool isEIP1559 = false}) async {
36 - if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) {
37 - throw DeviceNotConnectedException();
35 + if (ethereumLedgerApp == null) {
36 + throw exception.DeviceNotConnectedException();
37 }
38
40 - final sig = await ethereumLedgerApp!.signTransaction(device, payload);
39 + final sig = await ethereumLedgerApp!.signTransaction(payload);
40
41 final v = sig[0].toInt();
42 final r = bytesToHex(sig.sublist(1, 1 + 32));
@@ -65,14 +64,16 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
64 chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
65 }
66
68 - return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
67 + return MsgSignature(
68 + BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
69 }
70
71 @override
72 - Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
73 - if (isNotConnected) throw DeviceNotConnectedException();
72 + Future<Uint8List> signPersonalMessage(Uint8List payload,
73 + {int? chainId}) async {
74 + if (isNotConnected) throw exception.DeviceNotConnectedException();
75
75 - final sig = await ethereumLedgerApp!.signMessage(device, payload);
76 + final sig = await ethereumLedgerApp!.signMessage(payload);
77
78 final r = sig.sublist(1, 1 + 32);
79 final s = sig.sublist(1 + 32, 1 + 32 + 32);
@@ -84,20 +85,22 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
85
86 @override
87 Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
87 - throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
88 + throw UnimplementedError(
89 + "EvmLedgerCredentials.signPersonalMessageToUint8List");
90
89 - Future<void> provideERC20Info(String erc20ContractAddress, int chainId) async {
90 - if (isNotConnected) throw DeviceNotConnectedException();
91 + Future<void> provideERC20Info(
92 + String erc20ContractAddress, int chainId) async {
93 + if (isNotConnected) throw exception.DeviceNotConnectedException();
94
95 try {
93 - await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device,
96 + await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
97 erc20ContractAddress: erc20ContractAddress, chainId: chainId);
95 - } on LedgerException catch (e) {
96 - if (e.errorCode != -28672) rethrow;
98 + } catch (e) {
99 + print(e);
100 + rethrow;
101 + // if (e.errorCode != -28672) rethrow;
102 }
103 }
104
100 - bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null;
101 -
102 - LedgerDevice get device => ledgerDevice ?? ledger!.devices.first;
105 + bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
106 }
cw_evm/pubspec.yaml
+3 -6
@@ -25,20 +25,17 @@ dependencies:
25 mobx: ^2.0.7+4
26 cw_core:
27 path: ../cw_core
28 - ledger_flutter: ^1.0.1
28 + ledger_flutter_plus: ^1.4.1
29 ledger_ethereum:
30 git:
31 - url: https://github.com/cake-tech/ledger-ethereum.git
31 + url: https://github.com/cake-tech/ledger-flutter-plus-plugins
32 + path: packages/ledger-ethereum
33
34 dependency_overrides:
35 web3dart:
36 git:
37 url: https://github.com/cake-tech/web3dart.git
38 ref: cake
38 - ledger_flutter:
39 - git:
40 - url: https://github.com/cake-tech/ledger-flutter.git
41 - ref: cake-v3
39 watcher: ^1.1.0
40
41 dev_dependencies:
lib/bitcoin/cw_bitcoin.dart
+18 -6
@@ -487,18 +487,30 @@ class CWBitcoin extends Bitcoin {
487 }
488
489 @override
490 - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
491 - (wallet as BitcoinWallet).setLedger(ledger, device);
490 + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
491 + (wallet as ElectrumWallet).setLedgerConnection(connection);
492 }
493
494 @override
495 - Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
495 + Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM,
496 {int index = 0, int limit = 5}) async {
497 - final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
497 + final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection);
498 try {
499 return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
500 - } on LedgerException catch (err) {
501 - print(err.message);
500 + } catch (err) {
501 + print(err);
502 + throw err;
503 + }
504 + }
505 +
506 + @override
507 + Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM,
508 + {int index = 0, int limit = 5}) async {
509 + final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection);
510 + try {
511 + return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
512 + } catch (err) {
513 + print(err);
514 throw err;
515 }
516 }
lib/entities/hardware_wallet/hardware_wallet_device.dart new
+65
@@ -0,0 +1,65 @@
1 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
2 +
3 +class HardwareWalletDevice {
4 + final String name;
5 + final HardwareWalletDeviceType type;
6 + final HardwareWalletConnectionType connectionType;
7 +
8 + const HardwareWalletDevice({
9 + required this.name,
10 + required this.type,
11 + required this.connectionType,
12 + });
13 +
14 + factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
15 + HardwareWalletDevice(
16 + name: device.name,
17 + type: device.deviceInfo.toGeneric(),
18 + connectionType: device.connectionType.toGeneric(),
19 + );
20 +}
21 +
22 +enum HardwareWalletDeviceType {
23 + ledgerBlue,
24 + ledgerNanoS,
25 + ledgerNanoX,
26 + ledgerNanoSPlus,
27 + ledgerStax,
28 + ledgerFlex;
29 +}
30 +
31 +enum HardwareWalletConnectionType {
32 + usb,
33 + ble,
34 + nfc;
35 +}
36 +
37 +extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
38 + HardwareWalletDeviceType toGeneric() {
39 + switch (this) {
40 + case ledger.LedgerDeviceType.blue:
41 + return HardwareWalletDeviceType.ledgerBlue;
42 + case ledger.LedgerDeviceType.nanoS:
43 + return HardwareWalletDeviceType.ledgerNanoS;
44 + case ledger.LedgerDeviceType.nanoSP:
45 + return HardwareWalletDeviceType.ledgerNanoSPlus;
46 + case ledger.LedgerDeviceType.nanoX:
47 + return HardwareWalletDeviceType.ledgerNanoX;
48 + case ledger.LedgerDeviceType.stax:
49 + return HardwareWalletDeviceType.ledgerStax;
50 + case ledger.LedgerDeviceType.flex:
51 + return HardwareWalletDeviceType.ledgerFlex;
52 + }
53 + }
54 +}
55 +
56 +extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
57 + HardwareWalletConnectionType toGeneric() {
58 + switch (this) {
59 + case ledger.ConnectionType.usb:
60 + return HardwareWalletConnectionType.usb;
61 + case ledger.ConnectionType.ble:
62 + return HardwareWalletConnectionType.ble;
63 + }
64 + }
65 +}
lib/ethereum/cw_ethereum.dart
+8 -8
@@ -191,21 +191,21 @@ class CWEthereum extends Ethereum {
191 String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
192
193 @override
194 - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
195 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger(
196 - ledger,
197 - device.connectionType == ConnectionType.usb ? device : null,
198 - wallet.walletInfo.derivationInfo?.derivationPath);
194 + void setLedgerConnection(
195 + WalletBase wallet, ledger.LedgerConnection connection) {
196 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
197 + .setLedgerConnection(
198 + connection, wallet.walletInfo.derivationInfo?.derivationPath);
199 }
200
201 @override
202 Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
203 {int index = 0, int limit = 5}) async {
204 - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
204 + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
205 try {
206 return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
207 - } on LedgerException catch (err) {
208 - print(err.message);
207 + } catch (err) {
208 + print(err);
209 throw err;
210 }
211 }
lib/main.dart
+13 -1
@@ -1,4 +1,5 @@
1 import 'dart:async';
2 +import 'dart:io';
3 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
4 import 'package:cake_wallet/app_scroll_behavior.dart';
5 import 'package:cake_wallet/buy/order.dart';
@@ -43,6 +44,7 @@ import 'package:hive/hive.dart';
44 import 'package:cw_core/root_dir.dart';
45 import 'package:shared_preferences/shared_preferences.dart';
46 import 'package:cw_core/window_size.dart';
47 +import 'package:logging/logging.dart';
48
49 final navigatorKey = GlobalKey<NavigatorState>();
50 final rootKey = GlobalKey<RootState>();
@@ -68,8 +70,18 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
70 };
71 await initializeAppAtRoot();
72
71 - runApp(App(key: topLevelKey));
73 + if (kDebugMode) {
74 + final appDocDir = await getAppDir();
75 +
76 + final ledgerFile = File('${appDocDir.path}/ledger_log.txt');
77 + if (!ledgerFile.existsSync()) ledgerFile.createSync();
78 + Logger.root.onRecord.listen((event) async {
79 + final content = ledgerFile.readAsStringSync();
80 + ledgerFile.writeAsStringSync("$content\n${event.message}");
81 + });
82 + }
83
84 + runApp(App(key: topLevelKey));
85 isAppRunning = true;
86 }, (error, stackTrace) async {
87 if (!isAppRunning) {
lib/polygon/cw_polygon.dart
+8 -7
@@ -190,20 +190,21 @@ class CWPolygon extends Polygon {
190 String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
191
192 @override
193 - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
194 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger(
195 - ledger,
196 - device.connectionType == ConnectionType.usb ? device : null,
197 - wallet.walletInfo.derivationInfo?.derivationPath);
193 + void setLedgerConnection(
194 + WalletBase wallet, ledger.LedgerConnection connection) {
195 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
196 + .setLedgerConnection(
197 + connection, wallet.walletInfo.derivationInfo?.derivationPath);
198 }
199
200 @override
201 Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
202 {int index = 0, int limit = 5}) async {
203 - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
203 + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
204 try {
205 return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
206 - } on LedgerException catch (err) {
206 + } catch (err) {
207 + print(err);
208 throw err;
209 }
210 }
lib/src/screens/connect_device/connect_device_page.dart
+73 -47
@@ -3,15 +3,14 @@ import 'dart:io';
3
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/src/screens/base_page.dart';
6 -import 'package:cake_wallet/src/screens/connect_device/debug_device_page.dart';
6 import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
7 import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
8 import 'package:cake_wallet/utils/responsive_layout_util.dart';
9 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
10 import 'package:cw_core/wallet_type.dart';
11 import 'package:flutter/material.dart';
13 -import 'package:ledger_flutter/ledger_flutter.dart';
14 -import 'package:permission_handler/permission_handler.dart';
12 +import 'package:flutter_mobx/flutter_mobx.dart';
13 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
14
15 typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
16
@@ -19,7 +18,8 @@ class ConnectDevicePageParams {
18 final WalletType walletType;
19 final OnConnectDevice onConnectDevice;
20
22 - ConnectDevicePageParams({required this.walletType, required this.onConnectDevice});
21 + ConnectDevicePageParams(
22 + {required this.walletType, required this.onConnectDevice});
23 }
24
25 class ConnectDevicePage extends BasePage {
@@ -35,7 +35,8 @@ class ConnectDevicePage extends BasePage {
35 String get title => S.current.restore_title_from_hardware_wallet;
36
37 @override
38 - Widget body(BuildContext context) => ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
38 + Widget body(BuildContext context) =>
39 + ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
40 }
41
42 class ConnectDevicePageBody extends StatefulWidget {
@@ -43,47 +44,35 @@ class ConnectDevicePageBody extends StatefulWidget {
44 final OnConnectDevice onConnectDevice;
45 final LedgerViewModel ledgerVM;
46
46 - const ConnectDevicePageBody(this.walletType, this.onConnectDevice, this.ledgerVM);
47 + const ConnectDevicePageBody(
48 + this.walletType, this.onConnectDevice, this.ledgerVM);
49
50 @override
51 ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
52 }
53
54 class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
53 - final imageLedger = 'assets/images/ledger_nano.png';
54 -
55 - final ledger = Ledger(
56 - options: LedgerOptions(
57 - scanMode: ScanMode.balanced,
58 - maxScanDuration: const Duration(minutes: 5),
59 - ),
60 - onPermissionRequest: (_) async {
61 - Map<Permission, PermissionStatus> statuses = await [
62 - Permission.bluetoothScan,
63 - Permission.bluetoothConnect,
64 - Permission.bluetoothAdvertise,
65 - ].request();
66 -
67 - return statuses.values.where((status) => status.isDenied).isEmpty;
68 - },
69 - );
70 -
71 - var bleIsEnabled = true;
55 var bleDevices = <LedgerDevice>[];
56 var usbDevices = <LedgerDevice>[];
57
58 late Timer? _usbRefreshTimer = null;
59 late Timer? _bleRefreshTimer = null;
60 + late Timer? _bleStateTimer = null;
61 late StreamSubscription<LedgerDevice>? _bleRefresh = null;
62
63 @override
64 void initState() {
65 super.initState();
66 WidgetsBinding.instance.addPostFrameCallback((_) {
83 - _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
67 + _bleStateTimer = Timer.periodic(
68 + Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState());
69 +
70 + _bleRefreshTimer =
71 + Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
72
73 if (Platform.isAndroid) {
86 - _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
74 + _usbRefreshTimer =
75 + Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
76 }
77 });
78 }
@@ -91,35 +80,59 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
80 @override
81 void dispose() {
82 _bleRefreshTimer?.cancel();
83 + _bleStateTimer?.cancel();
84 _usbRefreshTimer?.cancel();
85 _bleRefresh?.cancel();
86 super.dispose();
87 }
88
89 Future<void> _refreshUsbDevices() async {
100 - final dev = await ledger.listUsbDevices();
90 + final dev = await widget.ledgerVM.ledgerPlusUSB.devices;
91 if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
92 + // _usbRefresh = widget.ledgerVM
93 + // .scanForUsbDevices()
94 + // .listen((device) => setState(() => usbDevices.add(device)))
95 + // ..onError((e) {
96 + // throw e.toString();
97 + // });
98 + // Keep polling until the lfp lib gets updated
99 + // _usbRefreshTimer?.cancel();
100 + // _usbRefreshTimer = null;
101 }
102
103 Future<void> _refreshBleDevices() async {
104 try {
106 - _bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device)))
105 + _bleRefresh = widget.ledgerVM
106 + .scanForBleDevices()
107 + .listen((device) => setState(() => bleDevices.add(device)))
108 ..onError((e) {
109 throw e.toString();
110 });
110 - setState(() => bleIsEnabled = true);
111 _bleRefreshTimer?.cancel();
112 _bleRefreshTimer = null;
113 } catch (e) {
114 - setState(() => bleIsEnabled = false);
114 + print(e);
115 }
116 }
117
118 Future<void> _connectToDevice(LedgerDevice device) async {
119 - await widget.ledgerVM.connectLedger(device);
119 + await widget.ledgerVM.connectLedger(device, widget.walletType);
120 widget.onConnectDevice(context, widget.ledgerVM);
121 }
122
123 + String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {
124 + switch (deviceInfo) {
125 + case LedgerDeviceType.nanoX:
126 + return 'assets/images/hardware_wallet/ledger_nano_x.png';
127 + case LedgerDeviceType.stax:
128 + return 'assets/images/hardware_wallet/ledger_stax.png';
129 + case LedgerDeviceType.flex:
130 + return 'assets/images/hardware_wallet/ledger_flex.png';
131 + default:
132 + return 'assets/images/hardware_wallet/ledger_nano_x.png';
133 + }
134 + }
135 +
136 @override
137 Widget build(BuildContext context) {
138 return Center(
@@ -139,7 +152,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
152 style: TextStyle(
153 fontSize: 16,
154 fontWeight: FontWeight.w500,
142 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
155 + color: Theme.of(context)
156 + .extension<CakeTextTheme>()!
157 + .titleColor),
158 textAlign: TextAlign.center,
159 ),
160 ),
@@ -152,18 +167,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
167 // title: "Debug Ledger",
168 // leading: imageLedger,
169 // ),
155 - if (!bleIsEnabled)
156 - Padding(
157 - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
158 - child: Text(
159 - S.of(context).ledger_please_enable_bluetooth,
160 - style: TextStyle(
161 - fontSize: 16,
162 - fontWeight: FontWeight.w500,
163 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
164 - textAlign: TextAlign.center,
170 + Observer(
171 + builder: (_) => Offstage(
172 + offstage: widget.ledgerVM.bleIsEnabled,
173 + child: Padding(
174 + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
175 + child: Text(
176 + S.of(context).ledger_please_enable_bluetooth,
177 + style: TextStyle(
178 + fontSize: 16,
179 + fontWeight: FontWeight.w500,
180 + color: Theme.of(context)
181 + .extension<CakeTextTheme>()!
182 + .titleColor),
183 + textAlign: TextAlign.center,
184 + ),
185 ),
186 ),
187 + ),
188 +
189 if (bleDevices.length > 0) ...[
190 Padding(
191 padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
@@ -174,7 +196,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
196 style: TextStyle(
197 fontSize: 14,
198 fontWeight: FontWeight.w400,
177 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
199 + color: Theme.of(context)
200 + .extension<CakeTextTheme>()!
201 + .titleColor,
202 ),
203 ),
204 ),
@@ -186,7 +210,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
210 child: DeviceTile(
211 onPressed: () => _connectToDevice(device),
212 title: device.name,
189 - leading: imageLedger,
213 + leading: _getDeviceTileLeading(device.deviceInfo),
214 connectionType: device.connectionType,
215 ),
216 ),
@@ -203,7 +227,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
227 style: TextStyle(
228 fontSize: 14,
229 fontWeight: FontWeight.w400,
206 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
230 + color: Theme.of(context)
231 + .extension<CakeTextTheme>()!
232 + .titleColor,
233 ),
234 ),
235 ),
@@ -215,7 +241,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
241 child: DeviceTile(
242 onPressed: () => _connectToDevice(device),
243 title: device.name,
218 - leading: imageLedger,
244 + leading: _getDeviceTileLeading(device.deviceInfo),
245 connectionType: device.connectionType,
246 ),
247 ),
lib/src/screens/connect_device/debug_device_page.dart
+68 -44
@@ -1,15 +1,15 @@
1 -// import 'dart:convert';
1 +// import 'dart:typed_data';
2 //
3 +// import 'package:basic_utils/basic_utils.dart';
4 +// import 'package:bitcoin_base/bitcoin_base.dart';
5 // import 'package:cake_wallet/src/screens/base_page.dart';
6 // import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
7 // import 'package:cake_wallet/src/widgets/primary_button.dart';
8 // import 'package:cake_wallet/utils/responsive_layout_util.dart';
7 -// import 'package:convert/convert.dart';
9 // import 'package:flutter/material.dart';
9 -// import 'package:ledger_bitcoin/ledger_bitcoin.dart';
10 -// import 'package:ledger_flutter/ledger_flutter.dart';
10 +// import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
11 +// import 'package:ledger_litecoin/ledger_litecoin.dart';
12 // import 'package:permission_handler/permission_handler.dart';
12 -// import 'package:polyseed/polyseed.dart';
13 //
14 // class DebugDevicePage extends BasePage {
15 // @override
@@ -50,7 +50,9 @@
50 // },
51 // );
52 //
53 -// late BitcoinLedgerApp btc;
53 +// // late BitcoinLedgerApp btc;
54 +// late LitecoinLedgerApp ltc;
55 +//
56 // var devices = <LedgerDevice>[];
57 // var status = "";
58 // var counter = 0;
@@ -59,7 +61,8 @@
61 // @override
62 // void initState() {
63 // super.initState();
62 -// btc = BitcoinLedgerApp(ledger);
64 +// // btc = BitcoinLedgerApp(ledger);
65 +// ltc = LitecoinLedgerApp(ledger);
66 // }
67 //
68 // @override
@@ -81,7 +84,7 @@
84 //
85 // @override
86 // Widget build(BuildContext context) {
84 -// final imageLedger = 'assets/images/ledger_nano.png';
87 +// final imageLedger = 'assets/images/hardware_wallet/ledger_nano_x.png';
88 //
89 // return Center(
90 // child: Container(
@@ -99,40 +102,25 @@
102 // DebugButton(
103 // title: "Get Version",
104 // method: "Version",
102 -// func: () async => await btc.getVersion(selectedDevice!),
103 -// ),
104 -// DebugButton(
105 -// title: "Get Master Fingerprint",
106 -// method: "Master Fingerprint",
107 -// func: () async => hex.encode(await btc.getMasterFingerprint(selectedDevice!)),
108 -// ),
109 -// DebugButton(
110 -// title: "Get XPub",
111 -// method: "XPub",
112 -// func: () async => await btc.getXPubKey(selectedDevice!, derivationPath: "m/84'/0'/$counter'"),
105 +// // func: () async => await btc.getVersion(selectedDevice!),
106 +// func: () async => await ltc.getVersion(selectedDevice!),
107 // ),
108 // DebugButton(
109 // title: "Get Wallet Address",
110 // method: "Wallet Address",
111 // func: () async {
112 // setState(() => counter++);
119 -// final derivationPath = "m/84'/0'/$counter'/0/0";
120 -// return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
113 +// final derivationPath = "m/84'/2'/0'/0/0";
114 +// return await ltc.getAccounts(selectedDevice!,
115 +// accountsDerivationPath: derivationPath);
116 +// // return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
117 // // return await ethereum!.getHardwareWalletAccounts(selectedDevice!);
122 -// },
118 +// },
119 // ),
120 // DebugButton(
121 // title: "Send Money",
126 -// method: "Sig",
127 -// func: () async {
128 -// final psbt = PsbtV2();
129 -// final psbtBuf = base64.decode(
130 -// "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"
131 -// );
132 -// psbt.deserialize(psbtBuf);
133 -// final result = await btc.signPsbt(selectedDevice!, psbt: psbt);
134 -// return result.toHexString();
135 -// },
122 +// method: "Raw Tx",
123 +// func: sendMoney
124 // ),
125 // Padding(
126 // padding: EdgeInsets.only(top: 20),
@@ -147,18 +135,18 @@
135 // ...devices
136 // .map(
137 // (device) => Padding(
150 -// padding: EdgeInsets.only(bottom: 20),
151 -// child: DeviceTile(
152 -// onPressed: () {
153 -// setState(() => selectedDevice = device);
154 -// ledger.connect(device);
155 -// },
156 -// title: device.name,
157 -// leading: imageLedger,
158 -// connectionType: device.connectionType,
159 -// ),
160 -// ),
161 -// )
138 +// padding: EdgeInsets.only(bottom: 20),
139 +// child: DeviceTile(
140 +// onPressed: () {
141 +// setState(() => selectedDevice = device);
142 +// ledger.connect(device);
143 +// },
144 +// title: device.name,
145 +// leading: imageLedger,
146 +// connectionType: device.connectionType,
147 +// ),
148 +// ),
149 +// )
150 // .toList(),
151 // PrimaryButton(
152 // text: "Refresh BLE",
@@ -188,6 +176,42 @@
176 // );
177 // }
178 //
179 +// Future<String> sendMoney() async {
180 +// final readyInputs = [
181 +// LedgerTransaction(
182 +// rawTx: "010000000001018c055c85c3724c98842d27712771dd0de139711f5940bba2df4615c5522184740000000017160014faf7f6dfb4e70798b92c93f33b4c51024491829df0ffffff022b05c70000000000160014f489f947fd13a1fb44ac168427081d3f30b6ce0cde9dd82e0000000017a914d5eca376cb49d65031220ff9093b7d407073ed0d8702483045022100f648c9f6a9b8f35b6ec29bbfae312c95ed3d56ce6a3f177d994efe90562ec4bd02205b82ce2c94bc0c9d152c3afc668b200bd82f48d6a14e83c66ba0f154cd5f69190121038f1dca119420d4aa7ad04af1c0d65304723789cccc56d335b18692390437f35900000000",
183 +// outputIndex: 0,
184 +// ownerPublicKey:
185 +// HexUtils.decode("03b2e67958ed3356e329e05cf94c3bee6b20c17175ac3b2a1278e073bf44f5d6ec"),
186 +// ownerDerivationPath: "m/84'/2'/0'/0/0",
187 +// sequence: 0xffffffff,
188 +// )
189 +// ];
190 +//
191 +// final outputs = [
192 +// BitcoinOutput(
193 +// address: P2wpkhAddress.fromAddress(
194 +// address: "ltc1qn0g5e36xaj07lqj6w9xn52ng07hud42g3jf5ps",
195 +// network: LitecoinNetwork.mainnet),
196 +// value: BigInt.from(1000000)),
197 +// BitcoinOutput(
198 +// address: P2wpkhAddress.fromAddress(
199 +// address: "ltc1qrx29qz4ghu4j0xk37ptgk7034cwpmjyxhrcnk9",
200 +// network: LitecoinNetwork.mainnet),
201 +// value: BigInt.from(12042705)),
202 +// ];
203 +// return await ltc.createTransaction(selectedDevice!,
204 +// inputs: readyInputs,
205 +// outputs: outputs
206 +// .map((e) => TransactionOutput.fromBigInt(
207 +// e.value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
208 +// .toList(),
209 +// sigHashType: 0x01,
210 +// additionals: ["bech32"],
211 +// isSegWit: true,
212 +// useTrustedInputForSegwit: true);
213 +// }
214 +//
215 // Widget DebugButton(
216 // {required String title, required String method, required Future<dynamic> Function() func}) {
217 // return Padding(
lib/src/screens/connect_device/widgets/device_tile.dart
+1 -1
@@ -1,6 +1,6 @@
1 import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
2 import 'package:flutter/material.dart';
3 -import 'package:ledger_flutter/ledger_flutter.dart';
3 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
4
5 class DeviceTile extends StatelessWidget {
6 const DeviceTile({
lib/src/screens/dashboard/pages/balance_page.dart
+1 -1
@@ -188,7 +188,7 @@ class CryptoBalanceWidget extends StatelessWidget {
188 Padding(
189 padding: const EdgeInsets.all(8.0),
190 child: Image.asset(
191 - 'assets/images/ledger_nano.png',
191 + 'assets/images/hardware_wallet/ledger_nano_x.png',
192 width: 24,
193 color: Theme.of(context)
194 .extension<DashboardPageTheme>()!
lib/src/screens/restore/restore_options_page.dart
+2 -2
@@ -67,7 +67,7 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
67 final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor;
68 final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor;
69 final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor;
70 - final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor);
70 + final imageLedger = Image.asset('assets/images/hardware_wallet/ledger_nano_x.png', width: 40, color: imageColor);
71 final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
72 final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
73
@@ -186,4 +186,4 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
186 }
187 }
188 }
189 -}
\ No newline at end of file
189 +}
lib/src/screens/send/send_page.dart
+7 -4
@@ -395,16 +395,19 @@ class SendPage extends BasePage {
395
396 if (sendViewModel.wallet.isHardwareWallet) {
397 if (!sendViewModel.ledgerViewModel!.isConnected) {
398 - await Navigator.of(context).pushNamed(Routes.connectDevices,
398 + await Navigator.of(context).pushNamed(
399 + Routes.connectDevices,
400 arguments: ConnectDevicePageParams(
401 walletType: sendViewModel.walletType,
402 onConnectDevice: (BuildContext context, _) {
402 - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
403 + sendViewModel.ledgerViewModel!
404 + .setLedger(sendViewModel.wallet);
405 Navigator.of(context).pop();
406 },
407 ));
408 } else {
407 - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
409 + sendViewModel.ledgerViewModel!
410 + .setLedger(sendViewModel.wallet);
411 }
412 }
413
@@ -509,7 +512,7 @@ class SendPage extends BasePage {
512 if (state is TransactionCommitted) {
513 newContactAddress =
514 newContactAddress ?? sendViewModel.newContactAddress();
512 -
515 +
516 if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) {
517 newContactAddress = null;
518 }
lib/view_model/dashboard/dashboard_view_model.dart
+1 -1
@@ -435,7 +435,7 @@ abstract class DashboardViewModelBase with Store {
435 }
436
437 @computed
438 - bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid);
438 + bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid) && !wallet.isHardwareWallet;
439
440 @computed
441 bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled;
lib/view_model/hardware_wallet/ledger_view_model.dart
+82 -29
@@ -1,3 +1,4 @@
1 +import 'dart:async';
2 import 'dart:io';
3
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
@@ -9,11 +10,19 @@ import 'package:cake_wallet/wallet_type_utils.dart';
10 import 'package:cw_core/hardware/device_connection_type.dart';
11 import 'package:cw_core/wallet_base.dart';
12 import 'package:cw_core/wallet_type.dart';
12 -import 'package:ledger_flutter/ledger_flutter.dart';
13 +
14 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
15 +import 'package:mobx/mobx.dart';
16 import 'package:permission_handler/permission_handler.dart';
17
15 -class LedgerViewModel {
16 - late final Ledger ledger;
18 +part 'ledger_view_model.g.dart';
19 +
20 +class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
21 +
22 +abstract class LedgerViewModelBase with Store {
23 + // late final Ledger ledger;
24 + late final sdk.LedgerInterface ledgerPlusBLE;
25 + late final sdk.LedgerInterface ledgerPlusUSB;
26
27 bool get _doesSupportHardwareWallets {
28 if (!DeviceInfo.instance.isMobile) {
@@ -21,53 +30,97 @@ class LedgerViewModel {
30 }
31
32 if (isMoneroOnly) {
24 - return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
33 + return DeviceConnectionType.supportedConnectionTypes(
34 + WalletType.monero, Platform.isIOS)
35 .isNotEmpty;
36 }
37
38 return true;
39 }
40
31 - LedgerViewModel() {
41 + LedgerViewModelBase() {
42 if (_doesSupportHardwareWallets) {
33 - ledger = Ledger(
34 - options: LedgerOptions(
35 - scanMode: ScanMode.balanced,
36 - maxScanDuration: const Duration(minutes: 5),
37 - ),
38 - onPermissionRequest: (_) async {
39 - Map<Permission, PermissionStatus> statuses = await [
40 - Permission.bluetoothScan,
41 - Permission.bluetoothConnect,
42 - Permission.bluetoothAdvertise,
43 - ].request();
44 -
45 - return statuses.values.where((status) => status.isDenied).isEmpty;
46 - },
47 - );
43 + reaction((_) => bleIsEnabled, (_) {
44 + if (bleIsEnabled) _initBLE();
45 + });
46 + updateBleState();
47 +
48 + if (!Platform.isIOS) {
49 + ledgerPlusUSB = sdk.LedgerInterface.usb();
50 + }
51 + }
52 + }
53 +
54 + @observable
55 + bool bleIsEnabled = false;
56 +
57 + bool _bleIsInitialized = false;
58 + Future<void> _initBLE() async {
59 + if (bleIsEnabled && !_bleIsInitialized) {
60 + ledgerPlusBLE = sdk.LedgerInterface.ble(onPermissionRequest: (_) async {
61 + Map<Permission, PermissionStatus> statuses = await [
62 + Permission.bluetoothScan,
63 + Permission.bluetoothConnect,
64 + Permission.bluetoothAdvertise,
65 + ].request();
66 +
67 + return statuses.values.where((status) => status.isDenied).isEmpty;
68 + });
69 + _bleIsInitialized = true;
70 }
71 }
72
51 - Future<void> connectLedger(LedgerDevice device) async {
52 - await ledger.connect(device);
73 + Future<void> updateBleState() async {
74 + final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
75 +
76 + final newState = bleState == sdk.AvailabilityState.poweredOn;
77 +
78 + if (newState != bleIsEnabled) bleIsEnabled = newState;
79 + }
80 +
81 + Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan();
82 +
83 + Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
84 +
85 + Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
86 + if (isConnected) {
87 + try {
88 + await _connection!.disconnect();
89 + } catch (_) {}
90 + }
91 + final ledger = device.connectionType == sdk.ConnectionType.ble
92 + ? ledgerPlusBLE
93 + : ledgerPlusUSB;
94 +
95 + if (_connectionChangeListener == null) {
96 + _connectionChangeListener = ledger.deviceStateChanges.listen((event) {
97 + print('Ledger Device State Changed: $event');
98 + if (event == sdk.BleConnectionState.disconnected) {
99 + _connection = null;
100 + _connectionChangeListener?.cancel();
101 + }
102 + });
103 + }
104
54 - if (device.connectionType == ConnectionType.usb) _device = device;
105 + _connection = await ledger.connect(device);
106 }
107
57 - LedgerDevice? _device;
108 + StreamSubscription<sdk.BleConnectionState>? _connectionChangeListener;
109 + sdk.LedgerConnection? _connection;
110
59 - bool get isConnected => ledger.devices.isNotEmpty || _device != null;
111 + bool get isConnected => _connection != null && !(_connection!.isDisconnected);
112
61 - LedgerDevice get device => _device ?? ledger.devices.first;
113 + sdk.LedgerConnection get connection => _connection!;
114
115 void setLedger(WalletBase wallet) {
116 switch (wallet.type) {
117 case WalletType.bitcoin:
66 - return bitcoin!.setLedger(wallet, ledger, device);
118 + case WalletType.litecoin:
119 + return bitcoin!.setLedgerConnection(wallet, connection);
120 case WalletType.ethereum:
68 - return ethereum!.setLedger(wallet, ledger, device);
121 + return ethereum!.setLedgerConnection(wallet, connection);
122 case WalletType.polygon:
70 - return polygon!.setLedger(wallet, ledger, device);
123 + return polygon!.setLedgerConnection(wallet, connection);
124 default:
125 throw Exception('Unexpected wallet type: ${wallet.type}');
126 }
lib/view_model/send/send_view_model.dart
+9 -10
@@ -24,7 +24,6 @@ import 'package:cw_core/unspent_coin_type.dart';
24 import 'package:cake_wallet/view_model/send/output.dart';
25 import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
26 import 'package:hive/hive.dart';
27 -import 'package:ledger_flutter/ledger_flutter.dart';
27 import 'package:mobx/mobx.dart';
28 import 'package:cake_wallet/entities/template.dart';
29 import 'package:cake_wallet/core/address_validator.dart';
@@ -408,16 +407,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
407 state = ExecutedSuccessfullyState();
408 return pendingTransaction;
409 } catch (e) {
411 - if (e is LedgerException) {
412 - final errorCode = e.errorCode.toRadixString(16);
413 - final fallbackMsg =
414 - e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode";
415 - final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg;
416 -
417 - state = FailureState(errorMsg);
418 - } else {
410 + // if (e is LedgerException) {
411 + // final errorCode = e.errorCode.toRadixString(16);
412 + // final fallbackMsg =
413 + // e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode";
414 + // final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg;
415 + //
416 + // state = FailureState(errorMsg);
417 + // } else {
418 state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
420 - }
419 + // }
420 }
421 return null;
422 }
lib/view_model/wallet_hardware_restore_view_model.dart
+9 -4
@@ -13,7 +13,6 @@ import 'package:cw_core/wallet_credentials.dart';
13 import 'package:cw_core/wallet_info.dart';
14 import 'package:cw_core/wallet_type.dart';
15 import 'package:hive/hive.dart';
16 -import 'package:ledger_flutter/ledger_flutter.dart';
16 import 'package:mobx/mobx.dart';
17
18 part 'wallet_hardware_restore_view_model.g.dart';
@@ -58,7 +57,11 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
57 switch (type) {
58 case WalletType.bitcoin:
59 accounts = await bitcoin!
61 - .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
60 + .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
61 + break;
62 + case WalletType.litecoin:
63 + accounts = await bitcoin!
64 + .getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
65 break;
66 case WalletType.ethereum:
67 accounts = await ethereum!
@@ -74,9 +77,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
77
78 availableAccounts.addAll(accounts);
79 _nextIndex += limit;
77 - } on LedgerException catch (e) {
78 - error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16));
80 + // } on LedgerException catch (e) {
81 + // error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16));
82 } catch (e) {
83 + print(e);
84 error = S.current.ledger_connection_error;
85 }
86
@@ -89,6 +93,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
93 WalletCredentials credentials;
94 switch (type) {
95 case WalletType.bitcoin:
96 + case WalletType.litecoin:
97 credentials =
98 bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!);
99 break;
macos/Flutter/GeneratedPluginRegistrant.swift
+2
@@ -17,6 +17,7 @@ import package_info_plus
17 import path_provider_foundation
18 import share_plus
19 import shared_preferences_foundation
20 +import universal_ble
21 import url_launcher_macos
22 import wakelock_plus
23
@@ -33,6 +34,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
34 PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
35 SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
36 SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
37 + UniversalBlePlugin.register(with: registry.registrar(forPlugin: "UniversalBlePlugin"))
38 UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
39 WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
40 }
pubspec_base.yaml
+3 -6
@@ -12,7 +12,7 @@ dependencies:
12 version: 4.0.2
13 shared_preferences: ^2.0.15
14 # provider: ^6.0.3
15 - rxdart: ^0.27.4
15 + rxdart: ^0.28.0
16 yaml: ^3.1.1
17 #barcode_scan: any
18 barcode_scan2: ^4.2.1
@@ -97,7 +97,7 @@ dependencies:
97 polyseed: ^0.0.6
98 nostr_tools: ^1.0.9
99 solana: ^0.30.1
100 - ledger_flutter: ^1.0.1
100 + ledger_flutter_plus: ^1.4.1
101 hashlib: ^1.19.2
102
103 dev_dependencies:
@@ -125,10 +125,6 @@ dependency_overrides:
125 bech32:
126 git:
127 url: https://github.com/cake-tech/bech32.git
128 - ledger_flutter:
129 - git:
130 - url: https://github.com/cake-tech/ledger-flutter.git
131 - ref: cake-v3
128 web3dart:
129 git:
130 url: https://github.com/cake-tech/web3dart.git
@@ -155,6 +151,7 @@ flutter:
151 assets:
152 - assets/images/
153 - assets/images/flags/
154 + - assets/images/hardware_wallet/
155 - assets/node_list.yml
156 - assets/haven_node_list.yml
157 - assets/bitcoin_electrum_server_list.yml
res/values/strings_de.arb
+37 -37
@@ -97,7 +97,7 @@
97 "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!",
98 "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.",
99 "cake_pay_save_order": "Die Karte sollte innerhalb von 1 Werktag an Ihre E-Mail gesendet werden, \n Ihre Bestell-ID zu speichern:",
100 - "cake_pay_subtitle": "Kaufen Sie weltweite Prepaid -Karten und Geschenkkarten",
100 + "cake_pay_subtitle": "Kaufen Sie weltweite Prepaid-Karten und Geschenkkarten",
101 "cake_pay_web_cards_subtitle": "Kaufen Sie weltweit Prepaid-Karten und Geschenkkarten",
102 "cake_pay_web_cards_title": "Cake Pay-Webkarten",
103 "cake_wallet": "Cake Wallet",
@@ -114,7 +114,7 @@
114 "change_currency": "Währung ändern",
115 "change_current_node": "Möchten Sie den aktuellen Knoten wirklich zu ${node}? ändern?",
116 "change_current_node_title": "Aktuellen Knoten ändern",
117 - "change_exchange_provider": "Swap -Anbieter ändern",
117 + "change_exchange_provider": "Swap-Anbieter ändern",
118 "change_language": "Sprache ändern",
119 "change_language_to": "Sprache zu ${language} ändern?",
120 "change_password": "Passwort ändern",
@@ -131,7 +131,7 @@
131 "choose_one": "Wähle ein",
132 "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
133 "choose_wallet_currency": "Bitte wählen Sie die Währung der Wallet:",
134 - "choose_wallet_group": "Wählen Sie Brieftaschengruppe",
134 + "choose_wallet_group": "Wählen Sie Walletgruppe",
135 "clear": "Zurücksetzen",
136 "clearnet_link": "Clearnet-Link",
137 "close": "Schließen",
@@ -179,7 +179,7 @@
179 "create_invoice": "Rechnung erstellen",
180 "create_new": "Neue Wallet erstellen",
181 "create_new_account": "Neues Konto erstellen",
182 - "create_new_seed": "Neue Samen erstellen",
182 + "create_new_seed": "Neue Seed erstellen",
183 "creating_new_wallet": "Neue Wallet erstellen",
184 "creating_new_wallet_error": "Fehler: ${description}",
185 "creation_date": "Erstellungsdatum",
@@ -189,9 +189,9 @@
189 "custom_value": "Benutzerdefinierten Wert",
190 "dark_theme": "Dunkel",
191 "debit_card": "Debitkarte",
192 - "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.",
192 + "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.",
193 "decimal_places_error": "Zu viele Nachkommastellen",
194 - "decimals_cannot_be_zero": "Token -Dezimalzahl kann nicht Null sein.",
194 + "decimals_cannot_be_zero": "Token-Dezimalzahl kann nicht Null sein.",
195 "default_buy_provider": "Standard-Kaufanbieter",
196 "default_sell_provider": "Standard-Verkaufsanbieter",
197 "delete": "Löschen",
@@ -236,7 +236,7 @@
236 "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin",
237 "email_address": "E-Mail-Adresse",
238 "enable": "Aktivieren",
239 - "enable_mempool_api": "Mempool -API für genaue Gebühren und Daten",
239 + "enable_mempool_api": "Mempool-API für genaue Gebühren und Daten",
240 "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee",
241 "enable_silent_payments_scanning": "Scannen Sie stille Zahlungen, bis die Spitze erreicht ist",
242 "enabled": "Ermöglicht",
@@ -245,7 +245,7 @@
245 "enter_code": "Code eingeben",
246 "enter_seed_phrase": "Geben Sie Ihre Seed-Phrase ein",
247 "enter_totp_code": "Bitte geben Sie den TOTP-Code ein.",
248 - "enter_wallet_password": "Geben Sie das Brieftaschenkennwort ein",
248 + "enter_wallet_password": "Geben Sie das Walletkennwort ein",
249 "enter_your_note": "Geben Sie Ihre Bemerkung ein…",
250 "enter_your_pin": "PIN eingeben",
251 "enter_your_pin_again": "Geben Sie Ihre PIN erneut ein",
@@ -282,7 +282,7 @@
282 "event": "Ereignis",
283 "events": "Veranstaltungen",
284 "exchange": "Tauschen",
285 - "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.",
285 + "exchange_incorrect_current_wallet_for_xmr": "Wenn Sie XMR aus Ihrer CakeWallet Monero-Balance tauschen möchten, wechseln Sie zuerst zu Ihrer Monero-Wallet.",
286 "exchange_new_template": "Neue Vorlage",
287 "exchange_provider_unsupported": "${providerName} wird nicht mehr unterstützt!",
288 "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 @@
309 "fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben",
310 "filter_by": "Filtern nach",
311 "first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Ethereum, Litecoin, und Haven",
312 - "fixed_pair_not_supported": "Dieses feste Paar wird nicht von den ausgewählten Swap -Diensten unterstützt",
312 + "fixed_pair_not_supported": "Dieses feste Paar wird nicht von den ausgewählten Swap-Diensten unterstützt",
313 "fixed_rate": "Feste Rate",
314 "fixed_rate_alert": "Sie können den Empfangsbetrag eingeben, wenn der Festratenmodus aktiviert ist. Möchten Sie in den Festratenmodus wechseln?",
315 "forgot_password": "Passwort vergessen",
@@ -349,9 +349,9 @@
349 "incoming": "Eingehend",
350 "incorrect_seed": "Der eingegebene Text ist ungültig.",
351 "inputs": "Eingänge",
352 - "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.",
353 - "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",
354 - "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",
352 + "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.",
353 + "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",
354 + "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",
355 "introducing_cake_pay": "Einführung von Cake Pay!",
356 "invalid_input": "Ungültige Eingabe",
357 "invalid_password": "Ungültiges Passwort",
@@ -365,20 +365,20 @@
365 "ledger_error_wrong_app": "Bitte stellen Sie sicher, dass Sie die richtige App auf Ihrem Ledger geöffnet haben",
366 "ledger_please_enable_bluetooth": "Bitte aktivieren Sie Bluetooth um sich mit Ihren Ledger zu verbinden.",
367 "light_theme": "Hell",
368 - "litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB -Scannen",
368 + "litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB-Scannen",
369 "litecoin_mweb": "MWeb",
370 "litecoin_mweb_always_scan": "Setzen Sie MWeb immer scannen",
371 - "litecoin_mweb_description": "MWEB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt",
371 + "litecoin_mweb_description": "MWWB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt",
372 "litecoin_mweb_dismiss": "Zurückweisen",
373 - "litecoin_mweb_display_card": "MWEB -Karte anzeigen",
373 + "litecoin_mweb_display_card": "MWEB-Karte anzeigen",
374 "litecoin_mweb_enable_later": "Sie können MWEB unter Anzeigeeinstellungen erneut aktivieren.",
375 "litecoin_mweb_pegin": "Peg in",
376 "litecoin_mweb_pegout": "Abstecken",
377 "litecoin_mweb_scanning": "MWEB Scanning",
378 - "litecoin_mweb_settings": "MWEB -Einstellungen",
379 - "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",
378 + "litecoin_mweb_settings": "MWEB-Einstellungen",
379 + "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",
380 "litecoin_what_is_mweb": "Was ist MWeb?",
381 - "live_fee_rates": "Live -Gebührenpreise über API",
381 + "live_fee_rates": "Live-Gebührenpreise über API",
382 "load_more": "Mehr laden",
383 "loading_your_wallet": "Wallet wird geladen",
384 "login": "Einloggen",
@@ -533,8 +533,8 @@
533 "rename": "Umbenennen",
534 "rep_warning": "Repräsentative Warnung",
535 "rep_warning_sub": "Ihr Vertreter scheint nicht gut zu sein. Tippen Sie hier, um eine neue auszuwählen",
536 - "repeat_wallet_password": "Wiederholen Sie das Brieftaschenkennwort",
537 - "repeated_password_is_incorrect": "Wiederholtes Passwort ist falsch. Bitte wiederholen Sie das Brieftaschenkennwort erneut.",
536 + "repeat_wallet_password": "Wiederholen Sie das Walletkennwort",
537 + "repeated_password_is_incorrect": "Wiederholtes Passwort ist falsch. Bitte wiederholen Sie das Walletkennwort erneut.",
538 "require_for_adding_contacts": "Erforderlich zum Hinzufügen von Kontakten",
539 "require_for_all_security_and_backup_settings": "Für alle Sicherheits- und Sicherungseinstellungen erforderlich",
540 "require_for_assessing_wallet": "Für den Zugriff auf die Wallet erforderlich",
@@ -576,7 +576,7 @@
576 "restore_wallet": "Wallet wiederherstellen",
577 "restore_wallet_name": "Walletname",
578 "restore_wallet_restore_description": "Beschreibung zur Wallet-Wiederherstellung",
579 - "robinhood_option_description": "Kaufen und übertragen Sie sofort mit Ihrem Debitkarten-, Bankkonto- oder Robinhood -Guthaben. Nur USA.",
579 + "robinhood_option_description": "Kaufen und übertragen Sie sofort mit Ihrem Debitkarten-, Bankkonto- oder Robinhood-Guthaben. Nur USA.",
580 "router_no_route": "Keine Route definiert für ${name}",
581 "save": "Speichern",
582 "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 @@
622 "seed_share": "Seed teilen",
623 "seed_title": "Seed",
624 "seedtype": "Seedtyp",
625 - "seedtype_alert_content": "Das Teilen von Samen mit anderen Brieftaschen ist nur mit bip39 Seedype möglich.",
626 - "seedtype_alert_title": "Seedype -Alarm",
625 + "seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.",
626 + "seedtype_alert_title": "Seedype-Alarm",
627 "seedtype_legacy": "Veraltet (25 Wörter)",
628 "seedtype_polyseed": "Polyseed (16 Wörter)",
629 "seedtype_wownero": "WOWNO (14 Wörter)",
@@ -691,7 +691,7 @@
691 "setup_your_debit_card": "Richten Sie Ihre Debitkarte ein",
692 "share": "Teilen",
693 "share_address": "Adresse teilen ",
694 - "shared_seed_wallet_groups": "Gemeinsame Samenbrieftaschengruppen",
694 + "shared_seed_wallet_groups": "Gemeinsame Walletsseed Gruppen",
695 "show_details": "Details anzeigen",
696 "show_keys": "Seed/Schlüssel anzeigen",
697 "show_market_place": "Marktplatz anzeigen",
@@ -711,12 +711,12 @@
711 "silent_payments_disclaimer": "Neue Adressen sind keine neuen Identitäten. Es ist eine Wiederverwendung einer bestehenden Identität mit einem anderen Etikett.",
712 "silent_payments_display_card": "Zeigen Sie stille Zahlungskarte",
713 "silent_payments_scan_from_date": "Scan ab Datum",
714 - "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.",
714 + "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.",
715 "silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen",
716 "silent_payments_scanned_tip": "Gescannt zum Trinkgeld! (${tip})",
717 "silent_payments_scanning": "Stille Zahlungen scannen",
718 "silent_payments_settings": "Einstellungen für stille Zahlungen",
719 - "single_seed_wallets_group": "Einzelne Samenbriefen",
719 + "single_seed_wallets_group": "Einzelne Wallets",
720 "slidable": "Verschiebbar",
721 "sort_by": "Sortiere nach",
722 "spend_key_private": "Spend Key (geheim)",
@@ -755,11 +755,11 @@
755 "syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert",
756 "template": "Vorlage",
757 "template_name": "Vorlagenname",
758 - "testnet_coins_no_value": "Testnet -Münzen haben keinen Wert",
758 + "testnet_coins_no_value": "Testnet-Münzen haben keinen Wert",
759 "third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
760 "third_intro_title": "Yat spielt gut mit anderen",
761 "thorchain_contract_address_not_supported": "Thorchain unterstützt das Senden an eine Vertragsadresse nicht",
762 - "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.",
762 + "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.",
763 "time": "${minutes}m ${seconds}s",
764 "tip": "Hinweis:",
765 "today": "Heute",
@@ -879,12 +879,12 @@
879 "voting_weight": "Stimmgewicht",
880 "waitFewSecondForTxUpdate": "Bitte warten Sie einige Sekunden, bis die Transaktion im Transaktionsverlauf angezeigt wird",
881 "waiting_payment_confirmation": "Warte auf Zahlungsbestätigung",
882 - "wallet_group": "Brieftaschengruppe",
883 - "wallet_group_description_four": "eine Brieftasche mit einem völlig neuen Samen schaffen.",
884 - "wallet_group_description_one": "In Kuchenbrieftasche können Sie eine erstellen",
885 - "wallet_group_description_three": "Sehen Sie den Bildschirm zur verfügbaren Brieftaschen und/oder Brieftaschengruppen. Oder wählen",
886 - "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",
887 - "wallet_group_empty_state_text_one": "Sieht so aus, als hätten Sie keine kompatiblen Brieftaschengruppen !\n\n TAP",
882 + "wallet_group": "Walletgruppe",
883 + "wallet_group_description_four": "eine Wallet mit einem völlig neuen Seed schaffen.",
884 + "wallet_group_description_one": "In CakeWallet können Sie eine erstellen",
885 + "wallet_group_description_three": "Sehen Sie den Bildschirm zur verfügbaren Wallet und/oder Walletgruppen. Oder wählen",
886 + "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",
887 + "wallet_group_empty_state_text_one": "Sieht so aus, als hätten Sie keine kompatiblen Walletgruppen !\n\n TAP",
888 "wallet_group_empty_state_text_two": "unten, um einen neuen zu machen.",
889 "wallet_keys": "Wallet-Seed/-Schlüssel",
890 "wallet_list_create_new_wallet": "Neue Wallet erstellen",
@@ -901,7 +901,7 @@
901 "wallet_menu": "Wallet-Menü",
902 "wallet_name": "Walletname",
903 "wallet_name_exists": "Wallet mit diesem Namen existiert bereits",
904 - "wallet_password_is_empty": "Brieftaschenkennwort ist leer. Brieftaschenkennwort sollte nicht leer sein",
904 + "wallet_password_is_empty": "Walletkennwort ist leer. Walletkennwort sollte nicht leer sein",
905 "wallet_recovery_height": "Erstellungshöhe",
906 "wallet_restoration_store_incorrect_seed_length": "Falsche Seed-Länge",
907 "wallet_seed": "Wallet-Seed",
@@ -941,4 +941,4 @@
941 "you_will_get": "Konvertieren zu",
942 "you_will_send": "Konvertieren von",
943 "yy": "YY"
944 -}
\ No newline at end of file
944 +}
tool/configure.dart
+9 -7
@@ -97,7 +97,7 @@ import 'package:cw_core/wallet_service.dart';
97 import 'package:cw_core/wallet_type.dart';
98 import 'package:cw_core/get_height_by_date.dart';
99 import 'package:hive/hive.dart';
100 -import 'package:ledger_flutter/ledger_flutter.dart';
100 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
101 import 'package:blockchain_utils/blockchain_utils.dart';
102 import 'package:bip39/bip39.dart' as bip39;
103 """;
@@ -121,6 +121,7 @@ import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
121 import 'package:cw_bitcoin/litecoin_wallet_service.dart';
122 import 'package:cw_bitcoin/litecoin_wallet.dart';
123 import 'package:cw_bitcoin/bitcoin_hardware_wallet_service.dart';
124 +import 'package:cw_bitcoin/litecoin_hardware_wallet_service.dart';
125 import 'package:mobx/mobx.dart';
126 """;
127 const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@@ -223,8 +224,9 @@ abstract class Bitcoin {
224 void deleteSilentPaymentAddress(Object wallet, String address);
225 Future<void> updateFeeRates(Object wallet);
226 int getMaxCustomFeeRate(Object wallet);
226 - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device);
227 - Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
227 + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
228 + Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
229 + Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
230 List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
231 bool txIsReceivedSilentPayment(TransactionInfo txInfo);
232 bool txIsMweb(TransactionInfo txInfo);
@@ -819,7 +821,7 @@ import 'package:cw_core/wallet_credentials.dart';
821 import 'package:cw_core/wallet_info.dart';
822 import 'package:cw_core/wallet_service.dart';
823 import 'package:hive/hive.dart';
822 -import 'package:ledger_flutter/ledger_flutter.dart';
824 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
825 import 'package:web3dart/web3dart.dart';
826
827 """;
@@ -885,7 +887,7 @@ abstract class Ethereum {
887 Web3Client? getWeb3Client(WalletBase wallet);
888 String getTokenAddress(CryptoCurrency asset);
889
888 - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device);
890 + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
891 Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
892 }
893 """;
@@ -923,7 +925,7 @@ import 'package:cw_core/wallet_credentials.dart';
925 import 'package:cw_core/wallet_info.dart';
926 import 'package:cw_core/wallet_service.dart';
927 import 'package:hive/hive.dart';
926 -import 'package:ledger_flutter/ledger_flutter.dart';
928 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
929 import 'package:web3dart/web3dart.dart';
930
931 """;
@@ -989,7 +991,7 @@ abstract class Polygon {
991 Web3Client? getWeb3Client(WalletBase wallet);
992 String getTokenAddress(CryptoCurrency asset);
993
992 - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device);
994 + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
995 Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
996 }
997 """;
windows/flutter/generated_plugin_registrant.cc
+3
@@ -11,6 +11,7 @@
11 #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
12 #include <permission_handler_windows/permission_handler_windows_plugin.h>
13 #include <share_plus/share_plus_windows_plugin_c_api.h>
14 +#include <universal_ble/universal_ble_plugin_c_api.h>
15 #include <url_launcher_windows/url_launcher_windows.h>
16
17 void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
25 registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
26 SharePlusWindowsPluginCApiRegisterWithRegistrar(
27 registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
28 + UniversalBlePluginCApiRegisterWithRegistrar(
29 + registry->GetRegistrarForPlugin("UniversalBlePluginCApi"));
30 UrlLauncherWindowsRegisterWithRegistrar(
31 registry->GetRegistrarForPlugin("UrlLauncherWindows"));
32 }
windows/flutter/generated_plugins.cmake
+1
@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
8 flutter_secure_storage_windows
9 permission_handler_windows
10 share_plus
11 + universal_ble
12 url_launcher_windows
13 )
14