From 8efd4a083314470e926b23b1b116b5d11d30f733 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 15 Jul 2024 18:01:22 +0200 Subject: [PATCH 01/19] Add Litecoin Hardware Wallet Creation --- cw_bitcoin/lib/electrum_wallet.dart | 2 +- .../lib/litecoin_hardware_wallet_service.dart | 44 +++++++++++++++++++ cw_bitcoin/lib/litecoin_network.dart | 2 +- cw_bitcoin/lib/litecoin_wallet.dart | 6 ++- cw_bitcoin/lib/litecoin_wallet_service.dart | 21 +++++++-- cw_bitcoin/pubspec.lock | 7 +++ cw_bitcoin/pubspec.yaml | 2 + .../lib/hardware/device_connection_type.dart | 1 + lib/bitcoin/cw_bitcoin.dart | 14 +++++- .../wallet_hardware_restore_view_model.dart | 7 ++- tool/configure.dart | 4 +- 11 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 cw_bitcoin/lib/litecoin_hardware_wallet_service.dart diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 6cc82780f6..748a39a4c7 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -118,7 +118,7 @@ abstract class ElectrumWalletBase .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)); } - return bitcoin.HDWallet.fromBase58(xpub!); + return bitcoin.HDWallet.fromBase58(xpub!, network: networkType); } static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart new file mode 100644 index 0000000000..4c1191eb6a --- /dev/null +++ b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart'; +import 'package:cw_bitcoin/litecoin_network.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_litecoin/ledger_litecoin.dart'; + +class LitecoinHardwareWalletService { + LitecoinHardwareWalletService(this.ledger, this.device); + + final Ledger ledger; + final LedgerDevice device; + + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final litecoinLedgerApp = LitecoinLedgerApp(ledger); + + final version = await litecoinLedgerApp.getVersion(device); + print(version); + + final accounts = []; + final indexRange = List.generate(limit, (i) => i + index); + + for (final i in indexRange) { + final derivationPath = "m/84'/2'/$i'"; + final xpub = await litecoinLedgerApp.getXPubKey(device, + accountsDerivationPath: derivationPath, xPubVersion: litecoinNetwork.bip32.public); + final hd = HDWallet.fromBase58(xpub, network: litecoinNetwork).derive(0); + + final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); + + accounts.add(HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + xpub: xpub, + )); + } + + return accounts; + } +} diff --git a/cw_bitcoin/lib/litecoin_network.dart b/cw_bitcoin/lib/litecoin_network.dart index d7ad2f837a..4b5ac83420 100644 --- a/cw_bitcoin/lib/litecoin_network.dart +++ b/cw_bitcoin/lib/litecoin_network.dart @@ -3,7 +3,7 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart'; final litecoinNetwork = NetworkType( messagePrefix: '\x19Litecoin Signed Message:\n', bech32: 'ltc', - bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + bip32: Bip32Type(public: 0x019da462, private: 0x0488ade4), pubKeyHash: 0x30, scriptHash: 0x32, wif: 0xb0); diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 209ddc774f..01cac3d23f 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -22,11 +22,12 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; abstract class LitecoinWalletBase extends ElectrumWallet with Store { LitecoinWalletBase({ - required String mnemonic, required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required Uint8List seedBytes, + Uint8List? seedBytes, + String? mnemonic, + String? xpub, String? addressPageType, List? initialAddresses, ElectrumBalance? initialBalance, @@ -35,6 +36,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { }) : super( mnemonic: mnemonic, password: password, + xpub: xpub, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, networkType: litecoinNetwork, diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index bb51a4eaa0..3576073afc 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:hive/hive.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; @@ -16,7 +17,7 @@ import 'package:bip39/bip39.dart' as bip39; class LitecoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, - BitcoinRestoreWalletFromWIFCredentials,BitcoinNewWalletCredentials> { + BitcoinRestoreWalletFromWIFCredentials,BitcoinRestoreWalletFromHardware> { LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; @@ -95,8 +96,22 @@ class LitecoinWalletService extends WalletService< } @override - Future restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) { - throw UnimplementedError("Restoring a Litecoin wallet from a hardware wallet is not yet supported!"); + Future restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials, + {bool? isTestnet}) async { + final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; + credentials.walletInfo?.network = network.value; + credentials.walletInfo?.derivationInfo?.derivationPath = + credentials.hwAccountData.derivationPath; + + final wallet = await LitecoinWallet( + password: credentials.password!, + xpub: credentials.hwAccountData.xpub, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + ); + await wallet.save(); + await wallet.init(); + return wallet; } @override diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 15f7cdb437..bbb3fe782b 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -504,6 +504,13 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + ledger_litecoin: + dependency: "direct main" + description: + path: "../../ledger_litecoin" + relative: true + source: path + version: "0.0.1" ledger_usb: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 66c5729e87..c99365caaa 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -45,6 +45,8 @@ dependencies: git: url: https://github.com/cake-tech/sp_scanner ref: sp_v2.0.0 + ledger_litecoin: + path: ../../ledger_litecoin dev_dependencies: diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart index 99fd5b1f0e..9a30695524 100644 --- a/cw_core/lib/hardware/device_connection_type.dart +++ b/cw_core/lib/hardware/device_connection_type.dart @@ -8,6 +8,7 @@ enum DeviceConnectionType { [bool isIOS = false]) { switch (walletType) { case WalletType.bitcoin: + case WalletType.litecoin: case WalletType.ethereum: case WalletType.polygon: if (isIOS) return [DeviceConnectionType.ble]; diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 86d9c49851..2c7a60ff6e 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -447,7 +447,7 @@ class CWBitcoin extends Bitcoin { } @override - Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, + Future> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device); try { @@ -458,6 +458,18 @@ class CWBitcoin extends Bitcoin { } } + @override + Future> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, + {int index = 0, int limit = 5}) async { + final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + try { + return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); + } on LedgerException catch (err) { + print(err.message); + throw err; + } + } + @override List getSilentPaymentAddresses(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index 804ef7e3cd..415f57d223 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -52,7 +52,11 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with switch (type) { case WalletType.bitcoin: accounts = await bitcoin! - .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit); + .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit); + break; + case WalletType.litecoin: + accounts = await bitcoin! + .getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit); break; case WalletType.ethereum: accounts = await ethereum! @@ -83,6 +87,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with WalletCredentials credentials; switch (type) { case WalletType.bitcoin: + case WalletType.litecoin: credentials = bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!); break; diff --git a/tool/configure.dart b/tool/configure.dart index 853d064486..3a26e16205 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -118,6 +118,7 @@ import 'package:cw_bitcoin/litecoin_wallet_service.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/bitcoin_hardware_wallet_service.dart'; +import 'package:cw_bitcoin/litecoin_hardware_wallet_service.dart'; import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; @@ -218,7 +219,8 @@ abstract class Bitcoin { int getMaxCustomFeeRate(Object wallet); void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); - Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + Future> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + Future> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); } """; From a24a733ce66bc63e7943d9e0d97dff96c609aec1 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 17 Jul 2024 18:47:35 +0200 Subject: [PATCH 02/19] Add Litecoin Hardware Wallet Creation --- cw_bitcoin/lib/bitcoin_wallet.dart | 1 + cw_bitcoin/lib/electrum_wallet.dart | 3 ++ cw_bitcoin/lib/litecoin_wallet.dart | 40 ++++++++++++++++++- lib/bitcoin/cw_bitcoin.dart | 2 +- .../hardware_wallet/ledger_view_model.dart | 1 + 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index d061480edf..70ca7f495f 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -195,6 +195,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { LedgerDevice? _ledgerDevice; BitcoinLedgerApp? _bitcoinLedgerApp; + @override void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { _ledger = setLedger; _ledgerDevice = setLedgerDevice; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 748a39a4c7..4b5eb2f410 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -42,6 +42,7 @@ import 'package:cw_core/get_height_by_date.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart' as http; +import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; @@ -975,6 +976,8 @@ abstract class ElectrumWalletBase } } + void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) => throw UnimplementedError(); + Future buildHardwareWalletTransaction({ required List outputs, required BigInt fee, diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 01cac3d23f..6806c99ea2 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -7,6 +7,8 @@ import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; +import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_litecoin/ledger_litecoin.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; @@ -106,13 +108,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet); return LitecoinWallet( - mnemonic: snp.mnemonic!, + mnemonic: snp.mnemonic, + xpub: snp.xpub, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp.addresses, initialBalance: snp.balance, - seedBytes: await mnemonicToSeedBytes(snp.mnemonic!), + seedBytes: snp.mnemonic != null ? await mnemonicToSeedBytes(snp.mnemonic!) : null, initialRegularAddressIndex: snp.regularAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex, addressPageType: snp.addressPageType, @@ -134,4 +137,37 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { return 0; } + + Ledger? _ledger; + LedgerDevice? _ledgerDevice; + LitecoinLedgerApp? _litecoinLedgerApp; + + @override + void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { + _ledger = setLedger; + _ledgerDevice = setLedgerDevice; + _litecoinLedgerApp = + LitecoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); + } + + @override + Future buildHardwareWalletTransaction({ + required List outputs, + required BigInt fee, + required BasedUtxoNetwork network, + required List utxos, + required Map publicKeys, + String? memo, + bool enableRBF = false, + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, + }) async { + for (final utxo in utxos) { + final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + + print(rawTx); + } + + throw UnimplementedError(); + } } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 2c7a60ff6e..86442a74ad 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -443,7 +443,7 @@ class CWBitcoin extends Bitcoin { @override void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { - (wallet as BitcoinWallet).setLedger(ledger, device); + (wallet as ElectrumWallet).setLedger(ledger, device); } @override diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index f05b1c8058..156a587c0f 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -63,6 +63,7 @@ class LedgerViewModel { void setLedger(WalletBase wallet) { switch (wallet.type) { case WalletType.bitcoin: + case WalletType.litecoin: return bitcoin!.setLedger(wallet, ledger, device); case WalletType.ethereum: return ethereum!.setLedger(wallet, ledger, device); From fbab1ee0e3fd77dd267b49b2514283316f22d068 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 17 Jul 2024 18:48:55 +0200 Subject: [PATCH 03/19] Fix Bitcoin not sending on Ledger --- cw_bitcoin/lib/electrum_wallet.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 6cc82780f6..39cf950092 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -519,17 +519,20 @@ abstract class ElectrumWalletBase ); spendsSilentPayment = true; isSilentPayment = true; - } else { + } else if (!isHardwareWallet) { privkey = generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network); } vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); - inputPrivKeyInfos.add(ECPrivateInfo( - privkey, - address.type == SegwitAddresType.p2tr, - tweak: !isSilentPayment, - )); + + if (privkey != null) { + inputPrivKeyInfos.add(ECPrivateInfo( + privkey, + address.type == SegwitAddresType.p2tr, + tweak: !isSilentPayment, + )); + } utxos.add( UtxoWithAddress( @@ -541,7 +544,7 @@ abstract class ElectrumWalletBase isSilentPayment: isSilentPayment, ), ownerDetails: UtxoAddressDetails( - publicKey: privkey.getPublic().toHex(), + publicKey: pubKeyHex, address: address, ), ), From 4f47d27e54c2044194066658eae61e393bee2db0 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 30 Jul 2024 15:02:18 +0200 Subject: [PATCH 04/19] Fixes to sending LTC using Ledger --- cw_bitcoin/lib/litecoin_wallet.dart | 46 ++++++-- cw_bitcoin/lib/psbt_transaction_builder.dart | 4 - cw_bitcoin/pubspec.yaml | 3 +- .../connect_device/connect_device_page.dart | 1 - .../connect_device/debug_device_page.dart | 108 +++++++++++------- 5 files changed, 104 insertions(+), 58 deletions(-) diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 6806c99ea2..cbdf76885c 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,22 +1,25 @@ +import 'dart:typed_data'; + +import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; -import 'package:bip39/bip39.dart' as bip39; part 'litecoin_wallet.g.dart'; @@ -162,12 +165,35 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, }) async { + final readyInputs = []; for (final utxo in utxos) { final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; print(rawTx); + + readyInputs.add(LedgerTransaction( + rawTx: rawTx, + outputIndex: utxo.utxo.vout, + ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)), + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, + sequence: enableRBF ? 0x1 : 0xffffffff, + )); } - throw UnimplementedError(); + final rawHex = await _litecoinLedgerApp!.createTransaction( + _ledgerDevice!, + inputs: readyInputs, + outputs: outputs + .map((e) => TransactionOutput.fromBigInt( + (e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) + .toList(), + sigHashType: 0x01, + additionals: ["bech32"], + isSegWit: true, + useTrustedInputForSegwit: true + ); + + return BtcTransaction.fromRaw(rawHex); } } diff --git a/cw_bitcoin/lib/psbt_transaction_builder.dart b/cw_bitcoin/lib/psbt_transaction_builder.dart index d8d2c9fac8..81efb792ea 100644 --- a/cw_bitcoin/lib/psbt_transaction_builder.dart +++ b/cw_bitcoin/lib/psbt_transaction_builder.dart @@ -16,10 +16,6 @@ class PSBTTransactionBuild { for (var i = 0; i < inputs.length; i++) { final input = inputs[i]; - print(input.utxo.isP2tr()); - print(input.utxo.isSegwit()); - print(input.utxo.isP2shSegwit()); - psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList())); psbt.setInputOutputIndex(i, input.utxo.vout); psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff); diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index c99365caaa..3fe63242de 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -46,7 +46,8 @@ dependencies: url: https://github.com/cake-tech/sp_scanner ref: sp_v2.0.0 ledger_litecoin: - path: ../../ledger_litecoin + git: + url: https://github.com/cake-tech/ledger-litecoin dev_dependencies: diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index a482b1c414..1ae3fa91e7 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/screens/connect_device/debug_device_page.dart'; import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; diff --git a/lib/src/screens/connect_device/debug_device_page.dart b/lib/src/screens/connect_device/debug_device_page.dart index f5a9ef2a44..7b59f91b7e 100644 --- a/lib/src/screens/connect_device/debug_device_page.dart +++ b/lib/src/screens/connect_device/debug_device_page.dart @@ -1,15 +1,15 @@ -// import 'dart:convert'; +// import 'dart:typed_data'; // +// import 'package:basic_utils/basic_utils.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; // import 'package:cake_wallet/src/screens/base_page.dart'; // import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; // import 'package:cake_wallet/src/widgets/primary_button.dart'; // import 'package:cake_wallet/utils/responsive_layout_util.dart'; -// import 'package:convert/convert.dart'; // import 'package:flutter/material.dart'; -// import 'package:ledger_bitcoin/ledger_bitcoin.dart'; // import 'package:ledger_flutter/ledger_flutter.dart'; +// import 'package:ledger_litecoin/ledger_litecoin.dart'; // import 'package:permission_handler/permission_handler.dart'; -// import 'package:polyseed/polyseed.dart'; // // class DebugDevicePage extends BasePage { // @override @@ -50,7 +50,9 @@ // }, // ); // -// late BitcoinLedgerApp btc; +// // late BitcoinLedgerApp btc; +// late LitecoinLedgerApp ltc; +// // var devices = []; // var status = ""; // var counter = 0; @@ -59,7 +61,8 @@ // @override // void initState() { // super.initState(); -// btc = BitcoinLedgerApp(ledger); +// // btc = BitcoinLedgerApp(ledger); +// ltc = LitecoinLedgerApp(ledger); // } // // @override @@ -99,40 +102,25 @@ // DebugButton( // title: "Get Version", // method: "Version", -// func: () async => await btc.getVersion(selectedDevice!), -// ), -// DebugButton( -// title: "Get Master Fingerprint", -// method: "Master Fingerprint", -// func: () async => hex.encode(await btc.getMasterFingerprint(selectedDevice!)), -// ), -// DebugButton( -// title: "Get XPub", -// method: "XPub", -// func: () async => await btc.getXPubKey(selectedDevice!, derivationPath: "m/84'/0'/$counter'"), +// // func: () async => await btc.getVersion(selectedDevice!), +// func: () async => await ltc.getVersion(selectedDevice!), // ), // DebugButton( // title: "Get Wallet Address", // method: "Wallet Address", // func: () async { // setState(() => counter++); -// final derivationPath = "m/84'/0'/$counter'/0/0"; -// return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath); +// final derivationPath = "m/84'/2'/0'/0/0"; +// return await ltc.getAccounts(selectedDevice!, +// accountsDerivationPath: derivationPath); +// // return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath); // // return await ethereum!.getHardwareWalletAccounts(selectedDevice!); -// }, +// }, // ), // DebugButton( // title: "Send Money", -// method: "Sig", -// func: () async { -// final psbt = PsbtV2(); -// final psbtBuf = base64.decode( -// "cHNidP8BAgQCAAAAAQQBAQEFAQIAAQ4gTW6k/cwKKu1u7m9oKr5ob7VcAC0IPkfaDitRi/FkD7sBDwQAAAAAARAE/////wEA/ekBAQAAAAABA9AYVQLI722H0osKMa/4dvMucrnKV1Myxtlp0l0BoOBDAQAAAAD/////ku6r2ABaHt9N26f/P4eMljX8t1f4lBcFfEwuNm/uXYoBAAAAAP////+YeAl8arEGKOcyrWJAYwSboyCstkhHN8zn7/vy7pkYTAEAAAAA/////wHlHgAAAAAAABYAFKdq0umSucBGVkl2MpT6Hgo/0a/xAkcwRAIgMkiJmNFbEi2I3CQYOwyV/JepCnFQRvj4xghkySpFcJMCIGAypkkWltfj+ucvqUIu27tusDAIAAB+rBhX/GV7hPlEASEDyLmWyTLjLfC9kn8pnW42jW5N6EJo5fObjWWEyfLDu9UCSDBFAiEAg9crVtwBPF+sWk+Th6pLwzDjJGItwsUCvoBPtmMTEb4CIDGuM7WOguV0TP21oidF3bSUZlEAjUHWfWzxLKw+3LofASEDfN16xKb70UZSeQyX5Tlh8iRq7np5Nlz9GYdcSU50sKwCSDBFAiEAvotOblaEiBptRWkvb6bj2MGyRjTphKLBLiHYmrRMTCgCIEKJH+z65uPSSz1NIb0d/u3bU9l0xcWk0idEsXjB+BIiASEDrAEiEtrSNKxbh6F/KPaCTafF2LVjCzb75WB+x4xSuoQAAAAAAQEf5R4AAAAAAAAWABSnatLpkrnARlZJdjKU+h4KP9Gv8SIGA3xMuxmPsBAm9aMEUBs3N46DB+Kdts3bZR/Wxt+uM0H4GKtN6bpUAACAAAAAgAAAAIAAAAAAAAAAAAABBBTk7bEOxYcdXDi1eeWraYDufm6eJgEDCOgDAAAAAAAAAAEEFDX3g/pnDXIfsRw8shK42NZn+SdpAQMIiBMAAAAAAAAiAgN8TLsZj7AQJvWjBFAbNzeOgwfinbbN22Uf1sbfrjNB+BirTem6VAAAgAAAAIAAAACAAAAAAAAAAAAA" -// ); -// psbt.deserialize(psbtBuf); -// final result = await btc.signPsbt(selectedDevice!, psbt: psbt); -// return result.toHexString(); -// }, +// method: "Raw Tx", +// func: sendMoney // ), // Padding( // padding: EdgeInsets.only(top: 20), @@ -147,18 +135,18 @@ // ...devices // .map( // (device) => Padding( -// padding: EdgeInsets.only(bottom: 20), -// child: DeviceTile( -// onPressed: () { -// setState(() => selectedDevice = device); -// ledger.connect(device); -// }, -// title: device.name, -// leading: imageLedger, -// connectionType: device.connectionType, -// ), -// ), -// ) +// padding: EdgeInsets.only(bottom: 20), +// child: DeviceTile( +// onPressed: () { +// setState(() => selectedDevice = device); +// ledger.connect(device); +// }, +// title: device.name, +// leading: imageLedger, +// connectionType: device.connectionType, +// ), +// ), +// ) // .toList(), // PrimaryButton( // text: "Refresh BLE", @@ -188,6 +176,42 @@ // ); // } // +// Future sendMoney() async { +// final readyInputs = [ +// LedgerTransaction( +// rawTx: "010000000001018c055c85c3724c98842d27712771dd0de139711f5940bba2df4615c5522184740000000017160014faf7f6dfb4e70798b92c93f33b4c51024491829df0ffffff022b05c70000000000160014f489f947fd13a1fb44ac168427081d3f30b6ce0cde9dd82e0000000017a914d5eca376cb49d65031220ff9093b7d407073ed0d8702483045022100f648c9f6a9b8f35b6ec29bbfae312c95ed3d56ce6a3f177d994efe90562ec4bd02205b82ce2c94bc0c9d152c3afc668b200bd82f48d6a14e83c66ba0f154cd5f69190121038f1dca119420d4aa7ad04af1c0d65304723789cccc56d335b18692390437f35900000000", +// outputIndex: 0, +// ownerPublicKey: +// HexUtils.decode("03b2e67958ed3356e329e05cf94c3bee6b20c17175ac3b2a1278e073bf44f5d6ec"), +// ownerDerivationPath: "m/84'/2'/0'/0/0", +// sequence: 0xffffffff, +// ) +// ]; +// +// final outputs = [ +// BitcoinOutput( +// address: P2wpkhAddress.fromAddress( +// address: "ltc1qn0g5e36xaj07lqj6w9xn52ng07hud42g3jf5ps", +// network: LitecoinNetwork.mainnet), +// value: BigInt.from(1000000)), +// BitcoinOutput( +// address: P2wpkhAddress.fromAddress( +// address: "ltc1qrx29qz4ghu4j0xk37ptgk7034cwpmjyxhrcnk9", +// network: LitecoinNetwork.mainnet), +// value: BigInt.from(12042705)), +// ]; +// return await ltc.createTransaction(selectedDevice!, +// inputs: readyInputs, +// outputs: outputs +// .map((e) => TransactionOutput.fromBigInt( +// e.value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) +// .toList(), +// sigHashType: 0x01, +// additionals: ["bech32"], +// isSegWit: true, +// useTrustedInputForSegwit: true); +// } +// // Widget DebugButton( // {required String title, required String method, required Future Function() func}) { // return Padding( From bad2be973936ae3d9d3dc7a01dea5d65e8de012f Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 12 Aug 2024 17:33:09 +0200 Subject: [PATCH 05/19] CW-679 Fix merge conflicts --- cw_bitcoin/lib/electrum_wallet.dart | 23 +++++++++++++------ cw_bitcoin/lib/electrum_wallet_addresses.dart | 2 ++ .../lib/litecoin_hardware_wallet_service.dart | 10 ++++---- cw_bitcoin/lib/litecoin_wallet.dart | 2 +- cw_bitcoin/pubspec.lock | 12 ++++++---- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 9e1421fff2..11df1c525b 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -25,6 +25,7 @@ import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -37,10 +38,9 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_core/get_height_by_date.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter/ledger_flutter.dart' as ledger; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; @@ -108,12 +108,13 @@ abstract class ElectrumWalletBase if (seedBytes != null) { return currency == CryptoCurrency.bch ? bitcoinCashHDWallet(seedBytes) - : Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( + : Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath( _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) as Bip32Slip10Secp256k1; } - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, network: networkType); + print(xpub); + return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network)); } static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => @@ -122,6 +123,15 @@ abstract class ElectrumWalletBase static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; + static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) { + switch (network) { + case LitecoinNetwork.mainnet: + return Bip44Conf.litecoinMainNet.altKeyNetVer; + default: + return null; + } + } + bool? alwaysScan; final Bip32Slip10Secp256k1 accountHD; @@ -456,7 +466,6 @@ abstract class ElectrumWalletBase } } - node!.isElectrs = false; node!.save(); return node!.isElectrs!; @@ -1032,7 +1041,8 @@ abstract class ElectrumWalletBase } } - void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) => throw UnimplementedError(); + void setLedger(ledger.Ledger setLedger, ledger.LedgerDevice setLedgerDevice) => + throw UnimplementedError(); Future buildHardwareWalletTransaction({ required List outputs, @@ -2261,4 +2271,3 @@ class UtxoDetails { required this.spendsUnconfirmedTX, }); } - diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index a0424c9345..402cc667fb 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -567,7 +567,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd; + bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; + bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => !addr.isHidden && !addr.isUsed && addr.type == type; diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart index 4c1191eb6a..e0e3890a44 100644 --- a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart @@ -1,8 +1,7 @@ import 'dart:async'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:ledger_flutter/ledger_flutter.dart'; @@ -22,14 +21,17 @@ class LitecoinHardwareWalletService { final accounts = []; final indexRange = List.generate(limit, (i) => i + index); + final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer; for (final i in indexRange) { final derivationPath = "m/84'/2'/$i'"; final xpub = await litecoinLedgerApp.getXPubKey(device, - accountsDerivationPath: derivationPath, xPubVersion: litecoinNetwork.bip32.public); - final hd = HDWallet.fromBase58(xpub, network: litecoinNetwork).derive(0); + accountsDerivationPath: derivationPath, + xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16)); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion); final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); + print(xpub); accounts.add(HardwareAccountData( address: address, diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 85c55f0cec..e13790a881 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -130,7 +130,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { return LitecoinWallet( mnemonic: keysData.mnemonic, - xpub: snp.xpub, + xpub: keysData.xPub, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 026664cce6..c119c6ede5 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -279,10 +279,10 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" ffigen: dependency: transitive description: @@ -490,9 +490,11 @@ packages: ledger_litecoin: dependency: "direct main" description: - path: "../../ledger_litecoin" - relative: true - source: path + path: "." + ref: HEAD + resolved-ref: f2e9ef4d2b73f3408d2796e703ea158e1bedfe16 + url: "https://github.com/cake-tech/ledger-litecoin" + source: git version: "0.0.1" ledger_usb: dependency: transitive From 17ec9b9ec21de304ea7d145329f71439a34ba948 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 12 Aug 2024 17:44:15 +0200 Subject: [PATCH 06/19] CW-679 Fix merge conflicts --- cw_bitcoin/lib/litecoin_hardware_wallet_service.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart index e0e3890a44..2a4f7ba7bf 100644 --- a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart @@ -28,10 +28,9 @@ class LitecoinHardwareWalletService { final xpub = await litecoinLedgerApp.getXPubKey(device, accountsDerivationPath: derivationPath, xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16)); - final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0)); final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); - print(xpub); accounts.add(HardwareAccountData( address: address, From 1c3db7f5499e1dcbd07936b8a3a4daf8a0dfb50c Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Thu, 15 Aug 2024 10:12:28 +0200 Subject: [PATCH 07/19] CW-679 Minor fixes --- cw_bitcoin/lib/electrum.dart | 2 +- cw_bitcoin/lib/litecoin_wallet.dart | 5 ++--- cw_bitcoin/lib/litecoin_wallet_service.dart | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 69b07d7c1c..1322adaf56 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -265,7 +265,7 @@ class ElectrumClient { try { final result = await callWithTimeout( method: 'blockchain.transaction.get', params: [hash, verbose], timeout: 10000); - if (result is Map) { + if (result is Map || result is String) { return result; } } on RequestFailedTimeoutException catch (_) { diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 152f5494ff..2863056901 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -204,14 +204,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; - print(rawTx); - readyInputs.add(LedgerTransaction( rawTx: rawTx, outputIndex: utxo.utxo.vout, ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)), ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - sequence: enableRBF ? 0x1 : 0xffffffff, + // sequence: enableRBF ? 0x1 : 0xffffffff, + sequence: 0xffffffff, )); } diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 37c01056b6..3f9a76fd5d 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -122,6 +122,7 @@ class LitecoinWalletService extends WalletService< xpub: credentials.hwAccountData.xpub, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); await wallet.save(); await wallet.init(); From 71abf6a6b84314b9246f292eb35f571bf916cc28 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Thu, 15 Aug 2024 15:32:29 +0200 Subject: [PATCH 08/19] CW-679 Add derivation Path of change address --- cw_bitcoin/lib/electrum_wallet.dart | 12 ++++++++++-- cw_bitcoin/lib/electrum_wallet_addresses.dart | 4 ++-- cw_bitcoin/lib/litecoin_wallet.dart | 8 ++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 5fa7b35d04..2ba6b332ff 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -5,7 +5,6 @@ import 'dart:isolate'; import 'dart:math'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_core/encryption_file_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:collection/collection.dart'; import 'package:cw_bitcoin/address_from_output.dart'; @@ -26,6 +25,7 @@ import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; @@ -769,12 +769,20 @@ abstract class ElectrumWalletBase } final changeAddress = await walletAddresses.getChangeAddress(); - final address = addressTypeFromStr(changeAddress, network); + final address = addressTypeFromStr(changeAddress.address, network); outputs.add(BitcoinOutput( address: address, value: BigInt.from(amountLeftForChangeAndFee), )); + // Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets + final changeDerivationPath = + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "/${changeAddress.isHidden ? "1" : "0"}" + "/${changeAddress.index}"; + utxoDetails.publicKeys[address.pubKeyHash()] = + PublicKeyWithDerivationPath('', changeDerivationPath); + int estimatedSize; if (network is BitcoinCashNetwork) { estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 402cc667fb..05d84179de 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -237,7 +237,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } @action - Future getChangeAddress() async { + Future getChangeAddress() async { updateChangeAddresses(); if (changeAddresses.isEmpty) { @@ -252,7 +252,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } updateChangeAddresses(); - final address = changeAddresses[currentChangeAddressIndex].address; + final address = changeAddresses[currentChangeAddressIndex]; currentChangeAddressIndex += 1; return address; } diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 2863056901..6b804718c5 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -214,6 +214,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { )); } + String? changePath; + for (final output in outputs) { + final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; + if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; + } + + final rawHex = await _litecoinLedgerApp!.createTransaction( _ledgerDevice!, inputs: readyInputs, @@ -221,6 +228,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { .map((e) => TransactionOutput.fromBigInt( (e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) .toList(), + changePath: changePath, sigHashType: 0x01, additionals: ["bech32"], isSegWit: true, From 3dc0bcf5e07141748be367800df92ad4bc87a3d2 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 16 Aug 2024 15:55:59 +0200 Subject: [PATCH 09/19] CW-679 Add BitcoinCash Support --- .../lib/src/bitcoin_cash_base.dart | 1 + .../bitcoin_cash_hardware_wallet_service.dart | 49 ++++++++++++ .../lib/src/bitcoin_cash_wallet.dart | 78 +++++++++++++++++-- ...coin_cash_wallet_creation_credentials.dart | 11 +++ .../lib/src/bitcoin_cash_wallet_service.dart | 48 ++++++++---- cw_bitcoin_cash/pubspec.yaml | 7 ++ .../lib/hardware/device_connection_type.dart | 1 + lib/bitcoin_cash/cw_bitcoin_cash.dart | 18 +++++ .../select_hardware_wallet_account_page.dart | 2 +- .../hardware_wallet/ledger_view_model.dart | 1 + .../wallet_hardware_restore_view_model.dart | 9 +++ tool/configure.dart | 11 ++- 12 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart index 4699b1649e..3a116d2d76 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart @@ -1,3 +1,4 @@ +export 'bitcoin_cash_hardware_wallet_service.dart'; export 'bitcoin_cash_wallet.dart'; export 'bitcoin_cash_wallet_addresses.dart'; export 'bitcoin_cash_wallet_creation_credentials.dart'; diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart new file mode 100644 index 0000000000..59e4ae3350 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart @@ -0,0 +1,49 @@ +import 'dart:async'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_litecoin/ledger_litecoin.dart'; + +class BitcoinCashHardwareWalletService { + BitcoinCashHardwareWalletService(this.ledger, this.device); + + final Ledger ledger; + final LedgerDevice device; + + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final bitcoinCashLedgerApp = LitecoinLedgerApp(ledger); + + final version = await bitcoinCashLedgerApp.getVersion(device); + print(version); + + final accounts = []; + final indexRange = List.generate(limit, (i) => i + index); + final xpubVersion = Bip44Conf.bitcoinCashMainNet.keyNetVer; + + for (final i in indexRange) { + final derivationPath = "m/44'/145'/$i'"; + final xpub = await bitcoinCashLedgerApp.getXPubKey( + device, + accountsDerivationPath: derivationPath, + xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16), + addressFormat: AddressFormat.cashaddr, + ); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0)); + + final address = generateP2PKHAddress(hd: hd, index: 0, network: BitcoinCashNetwork.mainnet); + + accounts.add(HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + xpub: xpub, + )); + } + + return + accounts; + } +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index a59569ae6c..117837e7e4 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -1,19 +1,21 @@ import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; +import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_litecoin/ledger_litecoin.dart'; import 'package:mobx/mobx.dart'; import 'bitcoin_cash_base.dart'; @@ -24,12 +26,13 @@ class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet; abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { BitcoinCashWalletBase({ - required String mnemonic, required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required Uint8List seedBytes, required EncryptionFileUtils encryptionFileUtils, + Uint8List? seedBytes, + String? mnemonic, + String? xpub, BitcoinAddressType? addressPageType, List? initialAddresses, ElectrumBalance? initialBalance, @@ -38,6 +41,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { }) : super( mnemonic: mnemonic, password: password, + xpub: xpub, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, network: BitcoinCashNetwork.mainnet, @@ -125,7 +129,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { } return BitcoinCashWallet( - mnemonic: keysData.mnemonic!, + mnemonic: keysData.mnemonic, + xpub: keysData.xPub, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, @@ -150,7 +155,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { } }).toList(), initialBalance: snp?.balance, - seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!), + seedBytes: keysData.mnemonic != null ? await MnemonicBip39.toSeed(keysData.mnemonic!) : null, encryptionFileUtils: encryptionFileUtils, initialRegularAddressIndex: snp?.regularAddressIndex, initialChangeAddressIndex: snp?.changeAddressIndex, @@ -214,4 +219,67 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { ); return priv.signMessage(StringUtils.encode(message)); } + + Ledger? _ledger; + LedgerDevice? _ledgerDevice; + LitecoinLedgerApp? _bitcoinCashLedgerApp; + + @override + void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { + _ledger = setLedger; + _ledgerDevice = setLedgerDevice; + _bitcoinCashLedgerApp = + LitecoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); + } + + @override + Future buildHardwareWalletTransaction({ + required List outputs, + required BigInt fee, + required BasedUtxoNetwork network, + required List utxos, + required Map publicKeys, + String? memo, + bool enableRBF = false, + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, + }) async { + final readyInputs = []; + for (final utxo in utxos) { + final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; + + readyInputs.add(LedgerTransaction( + rawTx: rawTx, + outputIndex: utxo.utxo.vout, + ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)), + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, + // sequence: enableRBF ? 0x1 : 0xffffffff, + sequence: 0xffffffff, + )); + } + + String? changePath; + for (final output in outputs) { + final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; + if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; + } + + + final rawHex = await _bitcoinCashLedgerApp!.createTransaction( + _ledgerDevice!, + inputs: readyInputs, + outputs: outputs + .map((e) => TransactionOutput.fromBigInt( + (e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) + .toList(), + changePath: changePath, + sigHashType: 0x01, + additionals: ["cashaddr"], + isSegWit: false, + useTrustedInputForSegwit: false + ); + + return BtcTransaction.fromRaw(rawHex); + } } diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart index 017040c5dc..f5ffe58473 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart @@ -1,3 +1,4 @@ +import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; @@ -24,3 +25,13 @@ class BitcoinCashRestoreWalletFromWIFCredentials extends WalletCredentials { final String wif; } + +class BitcoinCashRestoreWalletFromHardware extends WalletCredentials { + BitcoinCashRestoreWalletFromHardware({ + required String name, + required this.hwAccountData, + WalletInfo? walletInfo, + }) : super(name: name, walletInfo: walletInfo); + + final HardwareAccountData hwAccountData; +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart index a970be261d..8b1d761353 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:bip39/bip39.dart'; +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:collection/collection.dart'; import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/pathForWallet.dart'; @@ -9,14 +11,13 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:collection/collection.dart'; import 'package:hive/hive.dart'; class BitcoinCashWalletService extends WalletService< BitcoinCashNewWalletCredentials, BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, - BitcoinCashNewWalletCredentials> { + BitcoinCashRestoreWalletFromHardware> { BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect); final Box walletInfoSource; @@ -35,7 +36,7 @@ class BitcoinCashWalletService extends WalletService< final strength = credentials.seedPhraseLength == 24 ? 256 : 128; final wallet = await BitcoinCashWalletBase.create( - mnemonic: await MnemonicBip39.generate(strength: strength), + mnemonic: await MnemonicBip39.generate(strength: strength), password: credentials.password!, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, @@ -54,11 +55,11 @@ class BitcoinCashWalletService extends WalletService< try { final wallet = await BitcoinCashWalletBase.open( - password: password, - name: name, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); await wallet.init(); saveBackup(name); @@ -66,11 +67,11 @@ class BitcoinCashWalletService extends WalletService< } catch (_) { await restoreWalletFilesFromBackup(name); final wallet = await BitcoinCashWalletBase.open( - password: password, - name: name, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); await wallet.init(); return wallet; @@ -107,9 +108,24 @@ class BitcoinCashWalletService extends WalletService< } @override - Future restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) { - throw UnimplementedError( - "Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!"); + Future restoreFromHardwareWallet( + BitcoinCashRestoreWalletFromHardware credentials, + {bool? isTestnet}) async { + final network = isTestnet == true ? BitcoinCashNetwork.testnet : BitcoinCashNetwork.mainnet; + credentials.walletInfo?.network = network.value; + credentials.walletInfo?.derivationInfo?.derivationPath = + credentials.hwAccountData.derivationPath; + + final wallet = await BitcoinCashWallet( + password: credentials.password!, + xpub: credentials.hwAccountData.xpub, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + await wallet.save(); + await wallet.init(); + return wallet; } @override diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 3728bafc59..a733d478a5 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -33,6 +33,9 @@ dependencies: git: url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v2 + ledger_flutter: ^1.0.1 + ledger_litecoin: + path: ../../ledger_litecoin dev_dependencies: flutter_test: @@ -43,6 +46,10 @@ dev_dependencies: dependency_overrides: watcher: ^1.1.0 + ledger_flutter: + git: + url: https://github.com/cake-tech/ledger-flutter.git + ref: cake-v3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart index 9a30695524..80da1be03f 100644 --- a/cw_core/lib/hardware/device_connection_type.dart +++ b/cw_core/lib/hardware/device_connection_type.dart @@ -9,6 +9,7 @@ enum DeviceConnectionType { switch (walletType) { case WalletType.bitcoin: case WalletType.litecoin: + case WalletType.bitcoinCash: case WalletType.ethereum: case WalletType.polygon: if (isIOS) return [DeviceConnectionType.ble]; diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart index fcb34a286e..cafcbc1409 100644 --- a/lib/bitcoin_cash/cw_bitcoin_cash.dart +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -24,6 +24,12 @@ class CWBitcoinCash extends BitcoinCash { BitcoinCashRestoreWalletFromSeedCredentials( name: name, mnemonic: mnemonic, password: password); + @override + WalletCredentials createBitcoinCashHardwareWalletCredentials( + {required String name, required HardwareAccountData accountData, WalletInfo? walletInfo}) => + BitcoinCashRestoreWalletFromHardware( + name: name, hwAccountData: accountData, walletInfo: walletInfo); + @override TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) => BitcoinCashTransactionPriority.deserialize(raw: raw); @@ -37,4 +43,16 @@ class CWBitcoinCash extends BitcoinCash { @override TransactionPriority getBitcoinCashTransactionPrioritySlow() => BitcoinCashTransactionPriority.slow; + + @override + Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, + {int index = 0, int limit = 5}) async { + final hardwareWalletService = BitcoinCashHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + try { + return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); + } on LedgerException catch (err) { + print(err.message); + throw err; + } + } } diff --git a/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart b/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart index 31542ab5fa..f70a901b79 100644 --- a/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart +++ b/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart @@ -159,7 +159,7 @@ class _SelectHardwareWalletAccountFormState extends State Column( children: _walletHardwareRestoreVM.availableAccounts.map((acc) { - final address = acc.address; + final address = acc.address.split(":").last; return Padding( padding: EdgeInsets.only(top: 10), child: SelectButton( diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 156a587c0f..baa2b3eb1b 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -64,6 +64,7 @@ class LedgerViewModel { switch (wallet.type) { case WalletType.bitcoin: case WalletType.litecoin: + case WalletType.bitcoinCash: return bitcoin!.setLedger(wallet, ledger, device); case WalletType.ethereum: return ethereum!.setLedger(wallet, ledger, device); diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index 415f57d223..8e7daff16f 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -58,6 +59,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with accounts = await bitcoin! .getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit); break; + case WalletType.bitcoinCash: + accounts = await bitcoinCash! + .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit); + break; case WalletType.ethereum: accounts = await ethereum! .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit); @@ -91,6 +96,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with credentials = bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!); break; + case WalletType.bitcoinCash: + credentials = + bitcoinCash!.createBitcoinCashHardwareWalletCredentials(name: name, accountData: selectedAccount!); + break; case WalletType.ethereum: credentials = ethereum!.createEthereumHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!); diff --git a/tool/configure.dart b/tool/configure.dart index 6ef5ce75b5..a9e860de9f 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1001,13 +1001,16 @@ Future generateBitcoinCash(bool hasImplementation) async { const bitcoinCashCommonHeaders = """ import 'dart:typed_data'; -import 'package:cw_core/unspent_transaction_output.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; +import 'package:ledger_flutter/ledger_flutter.dart'; """; const bitcoinCashCWHeaders = """ import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; @@ -1027,6 +1030,9 @@ abstract class BitcoinCash { WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials( {required String name, required String mnemonic, required String password}); + WalletCredentials createBitcoinCashHardwareWalletCredentials( + {required String name, required HardwareAccountData accountData, WalletInfo? walletInfo}); + TransactionPriority deserializeBitcoinCashTransactionPriority(int raw); TransactionPriority getDefaultTransactionPriority(); @@ -1034,6 +1040,9 @@ abstract class BitcoinCash { List getTransactionPriorities(); TransactionPriority getBitcoinCashTransactionPrioritySlow(); + + Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, + {int index = 0, int limit = 5}); } """; From 557745e3d11d7ca3e5517a97cb588a47079c0444 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 16 Aug 2024 15:58:48 +0200 Subject: [PATCH 10/19] CW-679 Add BitcoinCash Support --- cw_bitcoin_cash/pubspec.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index a733d478a5..c112666267 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -35,7 +35,8 @@ dependencies: ref: cake-update-v2 ledger_flutter: ^1.0.1 ledger_litecoin: - path: ../../ledger_litecoin + git: + url: https://github.com/cake-tech/ledger-litecoin dev_dependencies: flutter_test: From 1c2de6c0805a03c367de7efdaf2138c3a11c02d4 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 16 Aug 2024 16:38:58 +0200 Subject: [PATCH 11/19] CW-679 Add BitcoinCash Default DerivationInfo --- lib/view_model/wallet_creation_vm.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index 5a9a1d093d..6336fabead 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -123,6 +123,13 @@ abstract class WalletCreationVMBase with Store { case WalletType.bitcoin: case WalletType.litecoin: return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first; + case WalletType.bitcoinCash: + return DerivationInfo( + derivationType: DerivationType.bip39, + derivationPath: "m/44'/145'/0'", + description: "Standard BIP44", + scriptType: "p2pkh", + ); default: return null; } From 1bf3f2449b9d57998211e482e574ef1e6cc22f3c Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 16 Sep 2024 14:16:58 +0200 Subject: [PATCH 12/19] ledger flutter plus refactoring --- .../lib/bitcoin_hardware_wallet_service.dart | 21 +- cw_bitcoin/lib/bitcoin_wallet.dart | 55 +-- cw_bitcoin/lib/electrum_wallet.dart | 391 +++++++++++------- .../lib/litecoin_hardware_wallet_service.dart | 23 +- cw_bitcoin/lib/litecoin_wallet.dart | 13 +- cw_bitcoin/pubspec.lock | 140 ++++--- cw_bitcoin/pubspec.yaml | 24 +- .../evm_chain_hardware_wallet_service.dart | 18 +- .../evm_chain_transaction_credentials.dart | 1 - cw_evm/lib/evm_ledger_credentials.dart | 57 +-- cw_evm/pubspec.yaml | 12 +- lib/bitcoin/cw_bitcoin.dart | 16 +- lib/ethereum/cw_ethereum.dart | 16 +- lib/main.dart | 11 + lib/polygon/cw_polygon.dart | 15 +- .../connect_device/connect_device_page.dart | 49 ++- .../connect_device/debug_device_page.dart | 2 +- .../connect_device/widgets/device_tile.dart | 2 +- lib/src/screens/send/send_page.dart | 222 ++++++---- lib/utils/exception_handler.dart | 10 +- .../hardware_wallet/ledger_view_model.dart | 51 ++- lib/view_model/send/send_view_model.dart | 19 +- .../wallet_hardware_restore_view_model.dart | 6 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec_base.yaml | 11 +- scripts/android/inject_app_details.sh | 10 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 28 files changed, 702 insertions(+), 499 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart index de339175d5..a02c51c69b 100644 --- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart @@ -5,30 +5,31 @@ import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; class BitcoinHardwareWalletService { - BitcoinHardwareWalletService(this.ledger, this.device); + BitcoinHardwareWalletService(this.ledgerConnection); - final Ledger ledger; - final LedgerDevice device; + final LedgerConnection ledgerConnection; - Future> getAvailableAccounts({int index = 0, int limit = 5}) async { - final bitcoinLedgerApp = BitcoinLedgerApp(ledger); + Future> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection); - final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device); - print(masterFp); + final masterFp = await bitcoinLedgerApp.getMasterFingerprint(); final accounts = []; final indexRange = List.generate(limit, (i) => i + index); for (final i in indexRange) { final derivationPath = "m/84'/0'/$i'"; - final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath); + final xpub = + await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath); Bip32Slip10Secp256k1 hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0)); - final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); + final address = generateP2WPKHAddress( + hd: hd, index: 0, network: BitcoinNetwork.mainnet); accounts.add(HardwareAccountData( address: address, diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index a209623322..9159ecb84d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -5,13 +5,13 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_bitcoin/psbt_transaction_builder.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/psbt_transaction_builder.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; @@ -19,7 +19,7 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; part 'bitcoin_wallet.g.dart'; @@ -61,8 +61,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialBalance: initialBalance, seedBytes: seedBytes, encryptionFileUtils: encryptionFileUtils, - currency: - networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, + currency: networkParam == BitcoinNetwork.testnet + ? CryptoCurrency.tbtc + : CryptoCurrency.btc, alwaysScan: alwaysScan, ) { // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) @@ -80,11 +81,13 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { mainHd: hd, sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: networkParam ?? network, - masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, + masterHd: + seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, ); autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + this.walletAddresses.isEnabledAutoGenerateSubaddress = + this.isEnabledAutoGenerateSubaddress; }); } @@ -185,8 +188,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { walletInfo.derivationInfo ??= DerivationInfo(); // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; + walletInfo.derivationInfo!.derivationPath ??= + snp?.derivationPath ?? electrum_path; + walletInfo.derivationInfo!.derivationType ??= + snp?.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; final mnemonic = keysData.mnemonic; @@ -228,16 +233,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { ); } - Ledger? _ledger; - LedgerDevice? _ledgerDevice; + LedgerConnection? _ledgerConnection; BitcoinLedgerApp? _bitcoinLedgerApp; @override - void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { - _ledger = setLedger; - _ledgerDevice = setLedgerDevice; - _bitcoinLedgerApp = - BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); + void setLedgerConnection(LedgerConnection connection) { + _ledgerConnection = connection; + _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!, + derivationPath: walletInfo.derivationInfo!.derivationPath!); } @override @@ -252,12 +255,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, }) async { - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!); + final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); final psbtReadyInputs = []; for (final utxo in utxos) { - final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); - final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; + final rawTx = + await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + final publicKeyAndDerivationPath = + publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; psbtReadyInputs.add(PSBTReadyUtxoWithAddress( utxo: utxo.utxo, @@ -269,10 +274,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { )); } - final psbt = - PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); + final psbt = PSBTTransactionBuild( + inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); - final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); + final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt); return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); } @@ -280,14 +285,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Future signMessage(String message, {String? address = null}) async { if (walletInfo.isHardwareWallet) { final addressEntry = address != null - ? walletAddresses.allAddresses.firstWhere((element) => element.address == address) + ? walletAddresses.allAddresses + .firstWhere((element) => element.address == address) : null; final index = addressEntry?.index ?? 0; final isChange = addressEntry?.isHidden == true ? 1 : 0; final accountPath = walletInfo.derivationInfo?.derivationPath; - final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; + final derivationPath = + accountPath != null ? "$accountPath/$isChange/$index" : null; - final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!, + final signature = await _bitcoinLedgerApp!.signMessage( message: ascii.encode(message), signDerivationPath: derivationPath); return base64Encode(signature); } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 5c1f1dc38d..9bd8490a52 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -40,7 +40,7 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart' as ledger; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; @@ -51,9 +51,10 @@ class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; const int TWEAKS_COUNT = 25; -abstract class ElectrumWalletBase - extends WalletBase - with Store, WalletKeysFile { +abstract class ElectrumWalletBase extends WalletBase< + ElectrumBalance, + ElectrumTransactionHistory, + ElectrumTransactionInfo> with Store, WalletKeysFile { ElectrumWalletBase({ required String password, required WalletInfo walletInfo, @@ -69,8 +70,8 @@ abstract class ElectrumWalletBase ElectrumBalance? initialBalance, CryptoCurrency? currency, this.alwaysScan, - }) : accountHD = - getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), + }) : accountHD = getAccountHDWallet( + currency, network, seedBytes, xpub, walletInfo.derivationInfo), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], @@ -78,16 +79,17 @@ abstract class ElectrumWalletBase isEnabledAutoGenerateSubaddress = true, unspentCoins = [], _scripthashesUpdateSubject = {}, - balance = ObservableMap.of(currency != null - ? { - currency: initialBalance ?? - ElectrumBalance( - confirmed: 0, - unconfirmed: 0, - frozen: 0, - ) - } - : {}), + balance = + ObservableMap.of(currency != null + ? { + currency: initialBalance ?? + ElectrumBalance( + confirmed: 0, + unconfirmed: 0, + frozen: 0, + ) + } + : {}), this.unspentCoinsInfo = unspentCoinsInfo, this.isTestnet = network == BitcoinNetwork.testnet, this._mnemonic = mnemonic, @@ -103,8 +105,12 @@ abstract class ElectrumWalletBase reaction((_) => syncStatus, _syncStatusReaction); } - static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network, - Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) { + static Bip32Slip10Secp256k1 getAccountHDWallet( + CryptoCurrency? currency, + BasedUtxoNetwork network, + Uint8List? seedBytes, + String? xpub, + DerivationInfo? derivationInfo) { if (seedBytes == null && xpub == null) { throw Exception( "To create a Wallet you need either a seed or an xpub. This should not happen"); @@ -113,17 +119,20 @@ abstract class ElectrumWalletBase if (seedBytes != null) { return currency == CryptoCurrency.bch ? bitcoinCashHDWallet(seedBytes) - : Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath( - _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) + : Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)) + .derivePath(_hardenedDerivationPath( + derivationInfo?.derivationPath ?? electrum_path)) as Bip32Slip10Secp256k1; } print(xpub); - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network)); + return Bip32Slip10Secp256k1.fromExtendedKey( + xpub!, getKeyNetVersion(network)); } static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => - Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1; + Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") + as Bip32Slip10Secp256k1; static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; @@ -165,7 +174,8 @@ abstract class ElectrumWalletBase @observable SyncStatus syncStatus; - Set get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet(); + Set get addressesSet => + walletAddresses.allAddresses.map((addr) => addr.address).toSet(); List get scriptHashes => walletAddresses.addressesByReceiveType .map((addr) => scriptHash(addr.address, network: network)) @@ -217,7 +227,8 @@ abstract class ElectrumWalletBase } if (tip > walletInfo.restoreHeight) { - _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); + _setListeners(walletInfo.restoreHeight, + chainTipParam: _currentChainTip); } } else { alwaysScan = false; @@ -228,7 +239,8 @@ abstract class ElectrumWalletBase syncStatus = SyncedSyncStatus(); } else { if (electrumClient.uri != null) { - await electrumClient.connectToUri(electrumClient.uri!, useSSL: electrumClient.useSSL); + await electrumClient.connectToUri(electrumClient.uri!, + useSSL: electrumClient.useSSL); startSync(); } } @@ -282,8 +294,8 @@ abstract class ElectrumWalletBase await transactionHistory.init(); await save(); - _autoSaveTimer = - Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); + _autoSaveTimer = Timer.periodic( + Duration(seconds: _autoSaveInterval), (_) async => await save()); } @action @@ -323,7 +335,8 @@ abstract class ElectrumWalletBase : null, labels: walletAddresses.labels, labelIndexes: walletAddresses.silentAddresses - .where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1) + .where((addr) => + addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1) .map((addr) => addr.index) .toList(), isSingleScan: doSingleScan ?? false, @@ -346,10 +359,11 @@ abstract class ElectrumWalletBase existingTxInfo.height = tx.height; final newUnspents = tx.unspents! - .where((unspent) => !(existingTxInfo.unspents?.any((element) => - element.hash.contains(unspent.hash) && - element.vout == unspent.vout && - element.value == unspent.value) ?? + .where((unspent) => !(existingTxInfo.unspents?.any( + (element) => + element.hash.contains(unspent.hash) && + element.vout == unspent.vout && + element.value == unspent.value) ?? false)) .toList(); @@ -360,7 +374,9 @@ abstract class ElectrumWalletBase existingTxInfo.unspents!.addAll(newUnspents); final newAmount = newUnspents.length > 1 - ? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent) + ? newUnspents + .map((e) => e.value) + .reduce((value, unspent) => value + unspent) : newUnspents[0].value; if (existingTxInfo.direction == TransactionDirection.incoming) { @@ -405,14 +421,15 @@ abstract class ElectrumWalletBase B_scan: silentAddress.B_scan, B_spend: unspent.silentPaymentLabel != null ? silentAddress.B_spend.tweakAdd( - BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)), + BigintUtils.fromBytes( + BytesUtils.fromHexString(unspent.silentPaymentLabel!)), ) : silentAddress.B_spend, network: network, ); - final addressRecord = walletAddresses.silentAddresses - .firstWhereOrNull((address) => address.address == silentPaymentAddress.toString()); + final addressRecord = walletAddresses.silentAddresses.firstWhereOrNull( + (address) => address.address == silentPaymentAddress.toString()); addressRecord?.txCount += 1; addressRecord?.balance += unspent.value; @@ -531,7 +548,8 @@ abstract class ElectrumWalletBase int get _dustAmount => 546; - bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; + bool _isBelowDust(int amount) => + amount <= _dustAmount && network != BitcoinNetwork.testnet; UtxoDetails _createUTXOS({ required bool sendAll, @@ -548,8 +566,10 @@ abstract class ElectrumWalletBase bool spendsUnconfirmedTX = false; int leftAmount = credentialsAmount; - final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList(); - final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); + final availableInputs = + unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList(); + final unconfirmedCoins = + availableInputs.where((utx) => utx.confirmations == 0).toList(); for (int i = 0; i < availableInputs.length; i++) { final utx = availableInputs[i]; @@ -569,11 +589,13 @@ abstract class ElectrumWalletBase ECPrivate? privkey; bool? isSilentPayment = false; - final hd = - utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; + final hd = utx.bitcoinAddressRecord.isHidden + ? walletAddresses.sideHd + : walletAddresses.mainHd; if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; + final unspentAddress = + utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( BigintUtils.fromBytes( BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), @@ -582,8 +604,8 @@ abstract class ElectrumWalletBase spendsSilentPayment = true; isSilentPayment = true; } else if (!isHardwareWallet) { - privkey = - generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network); + privkey = generateECPrivate( + hd: hd, index: utx.bitcoinAddressRecord.index, network: network); } vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); @@ -598,14 +620,18 @@ abstract class ElectrumWalletBase pubKeyHex = privkey.getPublic().toHex(); } else { - pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex(); + pubKeyHex = hd + .childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)) + .publicKey + .toHex(); } final derivationPath = "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" "/${utx.bitcoinAddressRecord.index}"; - publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); + publicKeys[address.pubKeyHash()] = + PublicKeyWithDerivationPath(pubKeyHex, derivationPath); utxos.add( UtxoWithAddress( @@ -691,7 +717,8 @@ abstract class ElectrumWalletBase int amount = utxoDetails.allInputsAmount - fee; if (amount <= 0) { - throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee); + throw BitcoinTransactionWrongBalanceException( + amount: utxoDetails.allInputsAmount + fee); } if (amount <= 0) { @@ -712,7 +739,8 @@ abstract class ElectrumWalletBase } if (outputs.length == 1) { - outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); + outputs[0] = BitcoinOutput( + address: outputs.last.address, value: BigInt.from(amount)); } return EstimatedTxResult( @@ -745,13 +773,16 @@ abstract class ElectrumWalletBase paysToSilentPayment: hasSilentPayment, ); - final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length; + final spendingAllCoins = + utxoDetails.availableInputs.length == utxoDetails.utxos.length; final spendingAllConfirmedCoins = !utxoDetails.spendsUnconfirmedTX && utxoDetails.utxos.length == - utxoDetails.availableInputs.length - utxoDetails.unconfirmedCoins.length; + utxoDetails.availableInputs.length - + utxoDetails.unconfirmedCoins.length; // How much is being spent - how much is being sent - int amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount; + int amountLeftForChangeAndFee = + utxoDetails.allInputsAmount - credentialsAmount; if (amountLeftForChangeAndFee <= 0) { if (!spendingAllCoins) { @@ -814,8 +845,8 @@ abstract class ElectrumWalletBase if (!_isBelowDust(amountLeftForChange)) { // Here, lastOutput already is change, return the amount left without the fee to the user's address. - outputs[outputs.length - 1] = - BitcoinOutput(address: lastOutput.address, value: BigInt.from(amountLeftForChange)); + outputs[outputs.length - 1] = BitcoinOutput( + address: lastOutput.address, value: BigInt.from(amountLeftForChange)); } else { // If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change outputs.removeLast(); @@ -843,7 +874,8 @@ abstract class ElectrumWalletBase } // Estimate to user how much is needed to send to cover the fee - final maxAmountWithReturningChange = utxoDetails.allInputsAmount - _dustAmount - fee - 1; + final maxAmountWithReturningChange = + utxoDetails.allInputsAmount - _dustAmount - fee - 1; throw BitcoinTransactionNoDustOnChangeException( bitcoinAmountToString(amount: maxAmountWithReturningChange), bitcoinAmountToString(amount: estimatedSendAll.amount), @@ -896,9 +928,11 @@ abstract class ElectrumWalletBase Future createTransaction(Object credentials) async { try { final outputs = []; - final transactionCredentials = credentials as BitcoinTransactionCredentials; + final transactionCredentials = + credentials as BitcoinTransactionCredentials; final hasMultiDestination = transactionCredentials.outputs.length > 1; - final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll; + final sendAll = + !hasMultiDestination && transactionCredentials.outputs.first.sendAll; final memo = transactionCredentials.outputs.first.memo; int credentialsAmount = 0; @@ -919,8 +953,8 @@ abstract class ElectrumWalletBase credentialsAmount += outputAmount; - final address = - addressTypeFromStr(out.isParsedAddress ? out.extractedAddress! : out.address, network); + final address = addressTypeFromStr( + out.isParsedAddress ? out.extractedAddress! : out.address, network); if (address is SilentPaymentAddress) { hasSilentPayment = true; @@ -930,7 +964,8 @@ abstract class ElectrumWalletBase // The value will be changed after estimating the Tx size and deducting the fee from the total to be sent outputs.add(BitcoinOutput(address: address, value: BigInt.from(0))); } else { - outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount))); + outputs.add(BitcoinOutput( + address: address, value: BigInt.from(outputAmount))); } } @@ -1011,7 +1046,8 @@ abstract class ElectrumWalletBase bool hasTaprootInputs = false; - final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { + final transaction = + txb.buildTransaction((txDigest, utxo, publicKey, sighash) { String error = "Cannot find private key."; ECPrivateInfo? key; @@ -1064,8 +1100,8 @@ abstract class ElectrumWalletBase transactionHistory.addOne(transaction); if (estimatedTx.spendsSilentPayment) { transactionHistory.transactions.values.forEach((tx) { - tx.unspents?.removeWhere( - (unspent) => estimatedTx.utxos.any((e) => e.utxo.txHash == unspent.hash)); + tx.unspents?.removeWhere((unspent) => + estimatedTx.utxos.any((e) => e.utxo.txHash == unspent.hash)); transactionHistory.addOne(tx); }); } @@ -1077,7 +1113,7 @@ abstract class ElectrumWalletBase } } - void setLedger(ledger.Ledger setLedger, ledger.LedgerDevice setLedgerDevice) => + void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError(); Future buildHardwareWalletTransaction({ @@ -1099,15 +1135,19 @@ abstract class ElectrumWalletBase 'passphrase': passphrase ?? '', 'account_index': walletAddresses.currentReceiveAddressIndexByType, 'change_address_index': walletAddresses.currentChangeAddressIndexByType, - 'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(), + 'addresses': + walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(), 'address_page_type': walletInfo.addressPageType == null ? SegwitAddresType.p2wpkh.toString() : walletInfo.addressPageType.toString(), 'balance': balance[currency]?.toJSON(), 'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index, 'derivationPath': walletInfo.derivationInfo?.derivationPath, - 'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(), - 'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(), + 'silent_addresses': walletAddresses.silentAddresses + .map((addr) => addr.toJSON()) + .toList(), + 'silent_address_index': + walletAddresses.currentSilentAddressIndex.toString(), }); int feeRate(TransactionPriority priority) { @@ -1122,11 +1162,14 @@ abstract class ElectrumWalletBase } } - int feeAmountForPriority(TransactionPriority priority, int inputsCount, int outputsCount, + int feeAmountForPriority( + TransactionPriority priority, int inputsCount, int outputsCount, {int? size}) => - feeRate(priority) * (size ?? estimatedTransactionSize(inputsCount, outputsCount)); + feeRate(priority) * + (size ?? estimatedTransactionSize(inputsCount, outputsCount)); - int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) => + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, + {int? size}) => feeRate * (size ?? estimatedTransactionSize(inputsCount, outputsCount)); @override @@ -1140,7 +1183,8 @@ abstract class ElectrumWalletBase return 0; } - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) { + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, + {int? outputsCount, int? size}) { if (size != null) { return feeAmountWithFeeRate(feeRate, 0, 0, size: size); } @@ -1184,26 +1228,33 @@ abstract class ElectrumWalletBase } final path = await makePath(); - await encryptionFileUtils.write(path: path, password: _password, data: toJSON()); + await encryptionFileUtils.write( + path: path, password: _password, data: toJSON()); await transactionHistory.save(); } @override Future renameWalletFiles(String newWalletName) async { - final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); + final currentWalletPath = + await pathForWallet(name: walletInfo.name, type: type); final currentWalletFile = File(currentWalletPath); - final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type); - final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName'); + final currentDirPath = + await pathForWalletDir(name: walletInfo.name, type: type); + final currentTransactionsFile = + File('$currentDirPath/$transactionsHistoryFileName'); // Copies current wallet files into new wallet name's dir and files if (currentWalletFile.existsSync()) { - final newWalletPath = await pathForWallet(name: newWalletName, type: type); + final newWalletPath = + await pathForWallet(name: newWalletName, type: type); await currentWalletFile.copy(newWalletPath); } if (currentTransactionsFile.existsSync()) { - final newDirPath = await pathForWalletDir(name: newWalletName, type: type); - await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName'); + final newDirPath = + await pathForWalletDir(name: newWalletName, type: type); + await currentTransactionsFile + .copy('$newDirPath/$transactionsHistoryFileName'); } // Delete old name's dir and files @@ -1316,8 +1367,10 @@ abstract class ElectrumWalletBase } @action - Future> fetchUnspent(BitcoinAddressRecord address) async { - final unspents = await electrumClient.getListUnspent(address.getScriptHash(network)); + Future> fetchUnspent( + BitcoinAddressRecord address) async { + final unspents = + await electrumClient.getListUnspent(address.getScriptHash(network)); List updatedUnspentCoins = []; @@ -1356,13 +1409,13 @@ abstract class ElectrumWalletBase Future _refreshUnspentCoinsInfo() async { try { final List keys = []; - final currentWalletUnspentCoins = - unspentCoinsInfo.values.where((element) => element.walletId.contains(id)); + final currentWalletUnspentCoins = unspentCoinsInfo.values + .where((element) => element.walletId.contains(id)); if (currentWalletUnspentCoins.isNotEmpty) { currentWalletUnspentCoins.forEach((element) { - final existUnspentCoins = unspentCoins - .where((coin) => element.hash.contains(coin.hash) && element.vout == coin.vout); + final existUnspentCoins = unspentCoins.where((coin) => + element.hash.contains(coin.hash) && element.vout == coin.vout); if (existUnspentCoins.isEmpty) { keys.add(element.key); @@ -1379,7 +1432,8 @@ abstract class ElectrumWalletBase } Future canReplaceByFee(String hash) async { - final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); + final verboseTransaction = + await electrumClient.getTransactionVerbose(hash: hash); final String? transactionHex; int confirmations = 0; @@ -1404,11 +1458,14 @@ abstract class ElectrumWalletBase final bundle = await getTransactionExpanded(hash: txId); final outputs = bundle.originalTransaction.outputs; - final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); + final changeAddresses = + walletAddresses.allAddresses.where((element) => element.isHidden); // look for a change address in the outputs - final changeOutput = outputs.firstWhereOrNull((output) => changeAddresses.any( - (element) => element.address == addressFromOutputScript(output.scriptPubKey, network))); + final changeOutput = outputs.firstWhereOrNull((output) => + changeAddresses.any((element) => + element.address == + addressFromOutputScript(output.scriptPubKey, network))); var allInputsAmount = 0; @@ -1420,17 +1477,19 @@ abstract class ElectrumWalletBase allInputsAmount += outTransaction.amount.toInt(); } - int totalOutAmount = bundle.originalTransaction.outputs - .fold(0, (previousValue, element) => previousValue + element.amount.toInt()); + int totalOutAmount = bundle.originalTransaction.outputs.fold( + 0, (previousValue, element) => previousValue + element.amount.toInt()); var currentFee = allInputsAmount - totalOutAmount; int remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee; - return changeOutput != null && changeOutput.amount.toInt() - remainingFee >= 0; + return changeOutput != null && + changeOutput.amount.toInt() - remainingFee >= 0; } - Future replaceByFee(String hash, int newFee) async { + Future replaceByFee( + String hash, int newFee) async { try { final bundle = await getTransactionExpanded(hash: hash); @@ -1445,15 +1504,18 @@ abstract class ElectrumWalletBase final inputTransaction = bundle.ins[i]; final vout = input.txIndex; final outTransaction = inputTransaction.outputs[vout]; - final address = addressFromOutputScript(outTransaction.scriptPubKey, network); + final address = + addressFromOutputScript(outTransaction.scriptPubKey, network); allInputsAmount += outTransaction.amount.toInt(); - final addressRecord = - walletAddresses.allAddresses.firstWhere((element) => element.address == address); + final addressRecord = walletAddresses.allAddresses + .firstWhere((element) => element.address == address); final btcAddress = addressTypeFromStr(addressRecord.address, network); final privkey = generateECPrivate( - hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + hd: addressRecord.isHidden + ? walletAddresses.sideHd + : walletAddresses.mainHd, index: addressRecord.index, network: network); @@ -1467,14 +1529,14 @@ abstract class ElectrumWalletBase vout: vout, scriptType: _getScriptType(btcAddress), ), - ownerDetails: - UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: btcAddress), + ownerDetails: UtxoAddressDetails( + publicKey: privkey.getPublic().toHex(), address: btcAddress), ), ); } - int totalOutAmount = bundle.originalTransaction.outputs - .fold(0, (previousValue, element) => previousValue + element.amount.toInt()); + int totalOutAmount = bundle.originalTransaction.outputs.fold(0, + (previousValue, element) => previousValue + element.amount.toInt()); var currentFee = allInputsAmount - totalOutAmount; int remainingFee = newFee - currentFee; @@ -1501,14 +1563,17 @@ abstract class ElectrumWalletBase continue; } - outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(newAmount))); + outputs.add( + BitcoinOutput(address: btcAddress, value: BigInt.from(newAmount))); } - final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); + final changeAddresses = + walletAddresses.allAddresses.where((element) => element.isHidden); // look for a change address in the outputs final changeOutput = outputs.firstWhereOrNull((output) => - changeAddresses.any((element) => element.address == output.address.toAddress(network))); + changeAddresses.any((element) => + element.address == output.address.toAddress(network))); // deduct the change amount from the output amount if (changeOutput != null) { @@ -1523,9 +1588,10 @@ abstract class ElectrumWalletBase enableRBF: true, ); - final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { - final key = - privateKeys.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey); + final transaction = + txb.buildTransaction((txDigest, utxo, publicKey, sighash) { + final key = privateKeys.firstWhereOrNull( + (element) => element.getPublic().toHex() == publicKey); if (key == null) { throw Exception("Cannot find private key"); @@ -1564,7 +1630,8 @@ abstract class ElectrumWalletBase int? time; int? confirmations; - final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); + final verboseTransaction = + await electrumClient.getTransactionVerbose(hash: hash); if (verboseTransaction.isEmpty) { transactionHex = await electrumClient.getTransactionHex(hash: hash); @@ -1576,7 +1643,8 @@ abstract class ElectrumWalletBase if (height != null) { if (time == null) { - time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round(); + time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000) + .round(); } if (confirmations == null) { @@ -1592,12 +1660,14 @@ abstract class ElectrumWalletBase final ins = []; for (final vin in original.inputs) { - final verboseTransaction = await electrumClient.getTransactionVerbose(hash: vin.txId); + final verboseTransaction = + await electrumClient.getTransactionVerbose(hash: vin.txId); final String inputTransactionHex; if (verboseTransaction.isEmpty) { - inputTransactionHex = await electrumClient.getTransactionHex(hash: hash); + inputTransactionHex = + await electrumClient.getTransactionHex(hash: hash); } else { inputTransactionHex = verboseTransaction['hex'] as String; } @@ -1638,21 +1708,24 @@ abstract class ElectrumWalletBase final Map historiesWithDetails = {}; if (type == WalletType.bitcoin) { - await Future.wait(ADDRESS_TYPES - .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); + await Future.wait(ADDRESS_TYPES.map((type) => + fetchTransactionsForAddressType(historiesWithDetails, type))); } else if (type == WalletType.bitcoinCash) { - await fetchTransactionsForAddressType(historiesWithDetails, P2pkhAddressType.p2pkh); + await fetchTransactionsForAddressType( + historiesWithDetails, P2pkhAddressType.p2pkh); } else if (type == WalletType.litecoin) { - await fetchTransactionsForAddressType(historiesWithDetails, SegwitAddresType.p2wpkh); + await fetchTransactionsForAddressType( + historiesWithDetails, SegwitAddresType.p2wpkh); } transactionHistory.transactions.values.forEach((tx) async { final isPendingSilentPaymentUtxo = - (tx.isPending || tx.confirmations == 0) && historiesWithDetails[tx.id] == null; + (tx.isPending || tx.confirmations == 0) && + historiesWithDetails[tx.id] == null; if (isPendingSilentPaymentUtxo) { - final info = - await fetchTransactionInfo(hash: tx.id, height: tx.height, retryOnFailure: true); + final info = await fetchTransactionInfo( + hash: tx.id, height: tx.height, retryOnFailure: true); if (info != null) { tx.confirmations = info.confirmations; @@ -1674,19 +1747,26 @@ abstract class ElectrumWalletBase Map historiesWithDetails, BitcoinAddressType type, ) async { - final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); - final hiddenAddresses = addressesByType.where((addr) => addr.isHidden == true); - final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false); + final addressesByType = + walletAddresses.allAddresses.where((addr) => addr.type == type); + final hiddenAddresses = + addressesByType.where((addr) => addr.isHidden == true); + final receiveAddresses = + addressesByType.where((addr) => addr.isHidden == false); await Future.wait(addressesByType.map((addressRecord) async { - final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip()); + final history = + await _fetchAddressHistory(addressRecord, await getCurrentChainTip()); if (history.isNotEmpty) { addressRecord.txCount = history.length; historiesWithDetails.addAll(history); - final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses; - final isUsedAddressUnderGap = matchedAddresses.toList().indexOf(addressRecord) >= + final matchedAddresses = + addressRecord.isHidden ? hiddenAddresses : receiveAddresses; + final isUsedAddressUnderGap = matchedAddresses + .toList() + .indexOf(addressRecord) >= matchedAddresses.length - (addressRecord.isHidden ? ElectrumWalletAddressesBase.defaultChangeAddressesCount @@ -1702,7 +1782,8 @@ abstract class ElectrumWalletBase (address) async { await _subscribeForUpdates(); return _fetchAddressHistory(address, await getCurrentChainTip()) - .then((history) => history.isNotEmpty ? address.address : null); + .then( + (history) => history.isNotEmpty ? address.address : null); }, type: type, ); @@ -1722,7 +1803,8 @@ abstract class ElectrumWalletBase try { final Map historiesWithDetails = {}; - final history = await electrumClient.getHistory(addressRecord.getScriptHash(network)); + final history = + await electrumClient.getHistory(addressRecord.getScriptHash(network)); if (history.isNotEmpty) { addressRecord.setAsUsed(); @@ -1736,13 +1818,15 @@ abstract class ElectrumWalletBase if (height > 0) { storedTx.height = height; // the tx's block itself is the first confirmation so add 1 - if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1; + if (currentHeight != null) + storedTx.confirmations = currentHeight - height + 1; storedTx.isPending = storedTx.confirmations == 0; } historiesWithDetails[txid] = storedTx; } else { - final tx = await fetchTransactionInfo(hash: txid, height: height, retryOnFailure: true); + final tx = await fetchTransactionInfo( + hash: txid, height: height, retryOnFailure: true); if (tx != null) { historiesWithDetails[txid] = tx; @@ -1773,7 +1857,10 @@ abstract class ElectrumWalletBase await getCurrentChainTip(); transactionHistory.transactions.values.forEach((tx) async { - if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) { + if (tx.unspents != null && + tx.unspents!.isNotEmpty && + tx.height != null && + tx.height! > 0) { tx.confirmations = await getCurrentChainTip() - tx.height! + 1; } }); @@ -1791,13 +1878,15 @@ abstract class ElectrumWalletBase Future _subscribeForUpdates() async { final unsubscribedScriptHashes = walletAddresses.allAddresses.where( - (address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)), + (address) => !_scripthashesUpdateSubject + .containsKey(address.getScriptHash(network)), ); await Future.wait(unsubscribedScriptHashes.map((address) async { final sh = address.getScriptHash(network); await _scripthashesUpdateSubject[sh]?.close(); - _scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh); + _scripthashesUpdateSubject[sh] = + await electrumClient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh]?.listen((event) async { try { await updateUnspents(address); @@ -1837,7 +1926,8 @@ abstract class ElectrumWalletBase transactionHistory.transactions.values.forEach((tx) { if (tx.unspents != null) { tx.unspents!.forEach((unspent) { - if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { + if (unspent.bitcoinAddressRecord + is BitcoinSilentPaymentAddressRecord) { if (unspent.isFrozen) totalFrozen += unspent.value; totalConfirmed += unspent.value; } @@ -1862,7 +1952,9 @@ abstract class ElectrumWalletBase } return ElectrumBalance( - confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen); + confirmed: totalConfirmed, + unconfirmed: totalUnconfirmed, + frozen: totalFrozen); } Future updateBalance() async { @@ -1873,7 +1965,8 @@ abstract class ElectrumWalletBase String getChangeAddress() { const minCountOfHiddenAddresses = 5; final random = Random(); - var addresses = walletAddresses.allAddresses.where((addr) => addr.isHidden).toList(); + var addresses = + walletAddresses.allAddresses.where((addr) => addr.isHidden).toList(); if (addresses.length < minCountOfHiddenAddresses) { addresses = walletAddresses.allAddresses.toList(); @@ -1883,12 +1976,15 @@ abstract class ElectrumWalletBase } @override - void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; + void setExceptionHandler(void Function(FlutterErrorDetails) onError) => + _onError = onError; @override Future signMessage(String message, {String? address = null}) async { final index = address != null - ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index + ? walletAddresses.allAddresses + .firstWhere((element) => element.address == address) + .index : null; final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); final priv = ECPrivate.fromWif( @@ -1901,7 +1997,8 @@ abstract class ElectrumWalletBase Future _setInitialHeight() async { if (_chainTipUpdateSubject != null) return; - if ((_currentChainTip == null || _currentChainTip! == 0) && walletInfo.restoreHeight == 0) { + if ((_currentChainTip == null || _currentChainTip! == 0) && + walletInfo.restoreHeight == 0) { await getUpdatedChainTip(); await walletInfo.updateRestoreHeight(_currentChainTip!); } @@ -1966,7 +2063,8 @@ abstract class ElectrumWalletBase _isTryingToConnect = true; Future.delayed(Duration(seconds: 10), () { - if (this.syncStatus is! SyncedSyncStatus && this.syncStatus is! SyncedTipSyncStatus) { + if (this.syncStatus is! SyncedSyncStatus && + this.syncStatus is! SyncedTipSyncStatus) { this.electrumClient.connectToUri( node!.uri, useSSL: node!.useSSL ?? false, @@ -1979,7 +2077,8 @@ abstract class ElectrumWalletBase // Message is shown on the UI for 3 seconds, revert to synced if (syncStatus is SyncedTipSyncStatus) { Timer(Duration(seconds: 3), () { - if (this.syncStatus is SyncedTipSyncStatus) this.syncStatus = SyncedSyncStatus(); + if (this.syncStatus is SyncedTipSyncStatus) + this.syncStatus = SyncedSyncStatus(); }); } } @@ -2051,7 +2150,8 @@ Future startRefresh(ScanData scanData) async { final syncingStatus = scanData.isSingleScan ? SyncingSyncStatus(1, 0) - : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight); + : SyncingSyncStatus.fromHeightValues( + scanData.chainTip, initialSyncHeight, syncHeight); // Initial status UI update, send how many blocks left to scan scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); @@ -2072,7 +2172,8 @@ Future startRefresh(ScanData scanData) async { scanData.labelIndexes.length, ); - tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight, count: count); + tweaksSubscription = + await electrumClient.tweaksSubscribe(height: syncHeight, count: count); tweaksSubscription?.listen((t) async { final tweaks = t as Map; @@ -2091,7 +2192,8 @@ Future startRefresh(ScanData scanData) async { for (var j = 0; j < blockTweaks.keys.length; j++) { final txid = blockTweaks.keys.elementAt(j); final details = blockTweaks[txid] as Map; - final outputPubkeys = (details["output_pubkeys"] as Map); + final outputPubkeys = + (details["output_pubkeys"] as Map); final tweak = details["tweak"].toString(); try { @@ -2226,7 +2328,8 @@ class EstimatedTxResult { final List utxos; final List inputPrivKeyInfos; - final Map publicKeys; // PubKey to derivationPath + final Map + publicKeys; // PubKey to derivationPath final int fee; final int amount; final bool spendsSilentPayment; @@ -2243,7 +2346,8 @@ class PublicKeyWithDerivationPath { final String publicKey; } -BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) { +BitcoinBaseAddress addressTypeFromStr( + String address, BasedUtxoNetwork network) { if (network is BitcoinCashNetwork) { if (!address.startsWith("bitcoincash:") && (address.startsWith("q") || address.startsWith("p"))) { @@ -2290,7 +2394,8 @@ class UtxoDetails { final List utxos; final List vinOutpoints; final List inputPrivKeyInfos; - final Map publicKeys; // PubKey to derivationPath + final Map + publicKeys; // PubKey to derivationPath final int allInputsAmount; final bool spendsSilentPayment; final bool spendsUnconfirmedTX; diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart index 2a4f7ba7bf..62840933c0 100644 --- a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart @@ -4,20 +4,19 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; class LitecoinHardwareWalletService { - LitecoinHardwareWalletService(this.ledger, this.device); + LitecoinHardwareWalletService(this.ledgerConnection); - final Ledger ledger; - final LedgerDevice device; + final LedgerConnection ledgerConnection; - Future> getAvailableAccounts({int index = 0, int limit = 5}) async { - final litecoinLedgerApp = LitecoinLedgerApp(ledger); + Future> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection); - final version = await litecoinLedgerApp.getVersion(device); - print(version); + await litecoinLedgerApp.getVersion(); final accounts = []; final indexRange = List.generate(limit, (i) => i + index); @@ -25,12 +24,14 @@ class LitecoinHardwareWalletService { for (final i in indexRange) { final derivationPath = "m/84'/2'/$i'"; - final xpub = await litecoinLedgerApp.getXPubKey(device, + final xpub = await litecoinLedgerApp.getXPubKey( accountsDerivationPath: derivationPath, xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16)); - final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0)); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion) + .childKey(Bip32KeyIndex(0)); - final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); + final address = generateP2WPKHAddress( + hd: hd, index: 0, network: LitecoinNetwork.mainnet); accounts.add(HardwareAccountData( address: address, diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 6b804718c5..ae48cec3ab 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -18,7 +18,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; import 'package:mobx/mobx.dart'; @@ -175,16 +175,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { return 0; } - Ledger? _ledger; - LedgerDevice? _ledgerDevice; + LedgerConnection? _ledgerConnection; LitecoinLedgerApp? _litecoinLedgerApp; @override - void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { - _ledger = setLedger; - _ledgerDevice = setLedgerDevice; + void setLedgerConnection(LedgerConnection connection) { + _ledgerConnection = connection; _litecoinLedgerApp = - LitecoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); + LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!); } @override @@ -222,7 +220,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final rawHex = await _litecoinLedgerApp!.createTransaction( - _ledgerDevice!, inputs: readyInputs, outputs: outputs .map((e) => TransactionOutput.fromBigInt( diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 1ac93de2cd..e8d9de019f 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -84,6 +84,14 @@ packages: url: "https://github.com/cake-tech/blockchain_utils" source: git version: "3.3.0" + bluez: + dependency: transitive + description: + name: bluez + sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce" + url: "https://pub.dev" + source: hosted + version: "0.8.2" boolean_selector: dependency: transitive description: @@ -276,6 +284,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" encrypt: dependency: transitive description: @@ -337,35 +353,27 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1+1" - flutter_reactive_ble: - dependency: transitive - description: - name: flutter_reactive_ble - sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0" - url: "https://pub.dev" - source: hosted - version: "5.3.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - frontend_server_client: + flutter_web_bluetooth: dependency: transitive description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + name: flutter_web_bluetooth + sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02" url: "https://pub.dev" source: hosted - version: "4.0.0" - functional_data: + version: "0.2.3" + frontend_server_client: dependency: transitive description: - name: functional_data - sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039" + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -489,38 +497,38 @@ packages: ledger_bitcoin: dependency: "direct main" description: - path: "." + path: "packages/ledger-bitcoin" ref: HEAD - resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab - url: "https://github.com/cake-tech/ledger-bitcoin" + resolved-ref: b139c9905b7b0cbdf62a107d0a995d43d928d29c + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" source: git version: "0.0.2" - ledger_flutter: + ledger_flutter_plus: dependency: "direct main" description: path: "." - ref: cake-v3 - resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4" - url: "https://github.com/cake-tech/ledger-flutter.git" + ref: cake-v1 + resolved-ref: b95aa11489abf3c2c8ab65c6d874237e3bf23479 + url: "https://github.com/cake-tech/ledger-flutter-plus" source: git - version: "1.0.2" + version: "1.2.0" ledger_litecoin: dependency: "direct main" description: - path: "." + path: "packages/ledger-litecoin" ref: HEAD - resolved-ref: f2e9ef4d2b73f3408d2796e703ea158e1bedfe16 - url: "https://github.com/cake-tech/ledger-litecoin" + resolved-ref: b139c9905b7b0cbdf62a107d0a995d43d928d29c + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" source: git version: "0.0.1" - ledger_usb: + ledger_usb_plus: dependency: transitive description: - name: ledger_usb - sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2" + name: ledger_usb_plus + sha256: f9f88253ca6d93559009b086ff45f0a4a0b34c1df477f653c59bc32562d16c65 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" logging: dependency: transitive description: @@ -557,10 +565,10 @@ packages: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" mobx: dependency: "direct main" description: @@ -649,6 +657,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -681,14 +697,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - protobuf: - dependency: transitive - description: - name: protobuf - sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" - url: "https://pub.dev" - source: hosted - version: "2.1.0" provider: dependency: transitive description: @@ -717,34 +725,18 @@ packages: dependency: transitive description: name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 url: "https://pub.dev" source: hosted - version: "3.2.1" - reactive_ble_mobile: - dependency: transitive - description: - name: reactive_ble_mobile - sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798" - url: "https://pub.dev" - source: hosted - version: "5.3.1" - reactive_ble_platform_interface: - dependency: transitive - description: - name: reactive_ble_platform_interface - sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813" - url: "https://pub.dev" - source: hosted - version: "5.3.1" + version: "3.2.2" rxdart: dependency: "direct main" description: name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "0.27.7" + version: "0.28.0" shelf: dependency: transitive description: @@ -879,6 +871,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_ble: + dependency: transitive + description: + name: universal_ble + sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3" + url: "https://pub.dev" + source: hosted + version: "0.12.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" unorm_dart: dependency: transitive description: @@ -935,6 +943,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: @@ -953,4 +969,4 @@ packages: version: "2.2.1" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.16.6" + flutter: ">=3.19.0" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index d7e3282b19..4b584d6fcf 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: git: url: https://github.com/cake-tech/bitbox-flutter.git ref: Add-Support-For-OP-Return-data - rxdart: ^0.27.5 + rxdart: ^0.28.0 cryptography: ^2.0.5 bitcoin_base: git: @@ -33,17 +33,25 @@ dependencies: git: url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v2 - ledger_flutter: ^1.0.1 - ledger_bitcoin: - git: - url: https://github.com/cake-tech/ledger-bitcoin sp_scanner: git: url: https://github.com/rafael-xmr/sp_scanner ref: sp_v4.0.0 + ledger_flutter_plus: + git: + url: https://github.com/cake-tech/ledger-flutter-plus + ref: cake-v1 + ledger_bitcoin: + git: + url: https://github.com/cake-tech/ledger-flutter-plus-plugins + path: ./packages/ledger-bitcoin ledger_litecoin: git: - url: https://github.com/cake-tech/ledger-litecoin + url: https://github.com/cake-tech/ledger-flutter-plus-plugins + path: ./packages/ledger-litecoin +# path: ../../ledger-litecoin +# git: +# url: https://github.com/cake-tech/ledger-litecoin dev_dependencies: @@ -55,10 +63,6 @@ dev_dependencies: hive_generator: ^1.1.3 dependency_overrides: - ledger_flutter: - git: - url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-v3 watcher: ^1.1.0 # For information on the generic Dart part of this file, see the diff --git a/cw_evm/lib/evm_chain_hardware_wallet_service.dart b/cw_evm/lib/evm_chain_hardware_wallet_service.dart index 6f0d11f2e5..d8f67c6415 100644 --- a/cw_evm/lib/evm_chain_hardware_wallet_service.dart +++ b/cw_evm/lib/evm_chain_hardware_wallet_service.dart @@ -2,26 +2,26 @@ import 'dart:async'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:ledger_ethereum/ledger_ethereum.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; class EVMChainHardwareWalletService { - EVMChainHardwareWalletService(this.ledger, this.device); + EVMChainHardwareWalletService(this.ledgerConnection); - final Ledger ledger; - final LedgerDevice device; + final LedgerConnection ledgerConnection; - Future> getAvailableAccounts({int index = 0, int limit = 5}) async { - final ethereumLedgerApp = EthereumLedgerApp(ledger); + Future> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection); - final version = await ethereumLedgerApp.getVersion(device); + await ethereumLedgerApp.getVersion(); final accounts = []; final indexRange = List.generate(limit, (i) => i + index); for (final i in indexRange) { final derivationPath = "m/44'/60'/$i'/0/0"; - final address = - await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath); + final address = await ethereumLedgerApp.getAccounts( + accountsDerivationPath: derivationPath); accounts.add(HardwareAccountData( address: address.first, diff --git a/cw_evm/lib/evm_chain_transaction_credentials.dart b/cw_evm/lib/evm_chain_transaction_credentials.dart index 02927cb4df..5b5bdf1700 100644 --- a/cw_evm/lib/evm_chain_transaction_credentials.dart +++ b/cw_evm/lib/evm_chain_transaction_credentials.dart @@ -1,7 +1,6 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; class EVMChainTransactionCredentials { EVMChainTransactionCredentials( diff --git a/cw_evm/lib/evm_ledger_credentials.dart b/cw_evm/lib/evm_ledger_credentials.dart index 0d8de17368..a0b7788dc8 100644 --- a/cw_evm/lib/evm_ledger_credentials.dart +++ b/cw_evm/lib/evm_ledger_credentials.dart @@ -1,17 +1,16 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:cw_core/hardware/device_not_connected_exception.dart'; +import 'package:cw_core/hardware/device_not_connected_exception.dart' + as exception; import 'package:ledger_ethereum/ledger_ethereum.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:web3dart/crypto.dart'; import 'package:web3dart/web3dart.dart'; class EvmLedgerCredentials extends CredentialsWithKnownAddress { final String _address; - Ledger? ledger; - LedgerDevice? ledgerDevice; EthereumLedgerApp? ethereumLedgerApp; EvmLedgerCredentials(this._address); @@ -19,25 +18,25 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { @override EthereumAddress get address => EthereumAddress.fromHex(_address); - void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) { - ledger = setLedger; - ledgerDevice = setLedgerDevice; - ethereumLedgerApp = - EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0"); + void setLedgerConnection(LedgerConnection connection, + [String? derivationPath]) { + ethereumLedgerApp = EthereumLedgerApp(connection, + derivationPath: derivationPath ?? "m/44'/60'/0'/0/0"); } @override - MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) => - throw UnimplementedError("EvmLedgerCredentials.signToEcSignature"); + MsgSignature signToEcSignature(Uint8List payload, + {int? chainId, bool isEIP1559 = false}) => + throw UnimplementedError("EvmLedgerCredentials.signToEcSignature"); @override Future signToSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) async { - if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) { - throw DeviceNotConnectedException(); + if (ethereumLedgerApp == null) { + throw exception.DeviceNotConnectedException(); } - final sig = await ethereumLedgerApp!.signTransaction(device, payload); + final sig = await ethereumLedgerApp!.signTransaction(payload); final v = sig[0].toInt(); final r = bytesToHex(sig.sublist(1, 1 + 32)); @@ -65,14 +64,16 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity; } - return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV); + return MsgSignature( + BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV); } @override - Future signPersonalMessage(Uint8List payload, {int? chainId}) async { - if (isNotConnected) throw DeviceNotConnectedException(); + Future signPersonalMessage(Uint8List payload, + {int? chainId}) async { + if (isNotConnected) throw exception.DeviceNotConnectedException(); - final sig = await ethereumLedgerApp!.signMessage(device, payload); + final sig = await ethereumLedgerApp!.signMessage(payload); final r = sig.sublist(1, 1 + 32); final s = sig.sublist(1 + 32, 1 + 32 + 32); @@ -84,20 +85,22 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { @override Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) => - throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List"); + throw UnimplementedError( + "EvmLedgerCredentials.signPersonalMessageToUint8List"); - Future provideERC20Info(String erc20ContractAddress, int chainId) async { - if (isNotConnected) throw DeviceNotConnectedException(); + Future provideERC20Info( + String erc20ContractAddress, int chainId) async { + if (isNotConnected) throw exception.DeviceNotConnectedException(); try { - await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device, + await ethereumLedgerApp!.getAndProvideERC20TokenInformation( erc20ContractAddress: erc20ContractAddress, chainId: chainId); - } on LedgerException catch (e) { - if (e.errorCode != -28672) rethrow; + } catch (e) { + print(e); + rethrow; + // if (e.errorCode != -28672) rethrow; } } - bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null; - - LedgerDevice get device => ledgerDevice ?? ledger!.devices.first; + bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected; } diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index b24e375a73..7c1d620172 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -23,20 +23,20 @@ dependencies: mobx: ^2.0.7+4 cw_core: path: ../cw_core - ledger_flutter: ^1.0.1 + ledger_flutter_plus: + git: + url: https://github.com/cake-tech/ledger-flutter-plus + ref: cake-v1 ledger_ethereum: git: - url: https://github.com/cake-tech/ledger-ethereum.git + url: https://github.com/cake-tech/ledger-flutter-plus-plugins + path: ./packages/ledger-ethereum dependency_overrides: web3dart: git: url: https://github.com/cake-tech/web3dart.git ref: cake - ledger_flutter: - git: - url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-v3 watcher: ^1.1.0 dev_dependencies: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 482a572c87..f2599ba373 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -444,18 +444,18 @@ class CWBitcoin extends Bitcoin { } @override - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { - (wallet as ElectrumWallet).setLedger(ledger, device); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { + (wallet as ElectrumWallet).setLedgerConnection(connection); } @override Future> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection); try { return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { - print(err.message); + } catch (err) { + print(err); throw err; } } @@ -463,11 +463,11 @@ class CWBitcoin extends Bitcoin { @override Future> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection); try { return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { - print(err.message); + } catch (err) { + print(err); throw err; } } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 4e210b2278..0f32cec992 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -175,21 +175,21 @@ class CWEthereum extends Ethereum { String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger( - ledger, - device.connectionType == ConnectionType.usb ? device : null, - wallet.walletInfo.derivationInfo?.derivationPath); + void setLedgerConnection( + WalletBase wallet, ledger.LedgerConnection connection) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) + .setLedgerConnection( + connection, wallet.walletInfo.derivationInfo?.derivationPath); } @override Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection); try { return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { - print(err.message); + } catch (err) { + print(err); throw err; } } diff --git a/lib/main.dart b/lib/main.dart index aeb76b3a87..059196fe91 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/app_scroll_behavior.dart'; import 'package:cake_wallet/buy/order.dart'; @@ -42,6 +43,7 @@ import 'package:hive/hive.dart'; import 'package:cw_core/root_dir.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cw_core/window_size.dart'; +import 'package:logging/logging.dart'; final navigatorKey = GlobalKey(); final rootKey = GlobalKey(); @@ -69,6 +71,15 @@ Future main() async { await initializeAppConfigs(); + final appDocDir = await getAppDir(); + + final ledgerFile = File('${appDocDir.path}/ledger_log.txt'); + if (!ledgerFile.existsSync()) ledgerFile.createSync(); + Logger.root.onRecord.listen((event) async { + final content = ledgerFile.readAsStringSync(); + ledgerFile.writeAsStringSync("$content\n${event.message}"); + }); + runApp(App()); isAppRunning = true; diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index 5bb87ff5bb..9d812eead1 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -173,20 +173,21 @@ class CWPolygon extends Polygon { String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger( - ledger, - device.connectionType == ConnectionType.usb ? device : null, - wallet.walletInfo.derivationInfo?.derivationPath); + void setLedgerConnection( + WalletBase wallet, ledger.LedgerConnection connection) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) + .setLedgerConnection( + connection, wallet.walletInfo.derivationInfo?.derivationPath); } @override Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection); try { return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { + } catch (err) { + print(err); throw err; } } diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index 1ae3fa91e7..48485b8e59 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -9,8 +9,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; -import 'package:permission_handler/permission_handler.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel); @@ -51,21 +50,21 @@ class ConnectDevicePageBody extends StatefulWidget { class ConnectDevicePageBodyState extends State { final imageLedger = 'assets/images/ledger_nano.png'; - final ledger = Ledger( - options: LedgerOptions( - scanMode: ScanMode.balanced, - maxScanDuration: const Duration(minutes: 5), - ), - onPermissionRequest: (_) async { - Map statuses = await [ - Permission.bluetoothScan, - Permission.bluetoothConnect, - Permission.bluetoothAdvertise, - ].request(); - - return statuses.values.where((status) => status.isDenied).isEmpty; - }, - ); + // final ledger = Ledger( + // options: LedgerOptions( + // scanMode: ScanMode.balanced, + // maxScanDuration: const Duration(minutes: 5), + // ), + // onPermissionRequest: (_) async { + // Map statuses = await [ + // Permission.bluetoothScan, + // Permission.bluetoothConnect, + // Permission.bluetoothAdvertise, + // ].request(); + // + // return statuses.values.where((status) => status.isDenied).isEmpty; + // }, + // ); var bleIsEnabled = true; var bleDevices = []; @@ -81,9 +80,9 @@ class ConnectDevicePageBodyState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); - if (Platform.isAndroid) { - _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); - } + // if (Platform.isAndroid) { + // _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); + // } }); } @@ -95,14 +94,14 @@ class ConnectDevicePageBodyState extends State { super.dispose(); } - Future _refreshUsbDevices() async { - final dev = await ledger.listUsbDevices(); - if (usbDevices.length != dev.length) setState(() => usbDevices = dev); - } + // Future _refreshUsbDevices() async { + // final dev = await ledger.listUsbDevices(); + // if (usbDevices.length != dev.length) setState(() => usbDevices = dev); + // } Future _refreshBleDevices() async { try { - _bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device))) + _bleRefresh = widget.ledgerVM.scanForDevices().listen((device) => setState(() => bleDevices.add(device))) ..onError((e) { throw e.toString(); }); diff --git a/lib/src/screens/connect_device/debug_device_page.dart b/lib/src/screens/connect_device/debug_device_page.dart index 7b59f91b7e..c9ae002f0b 100644 --- a/lib/src/screens/connect_device/debug_device_page.dart +++ b/lib/src/screens/connect_device/debug_device_page.dart @@ -7,7 +7,7 @@ // import 'package:cake_wallet/src/widgets/primary_button.dart'; // import 'package:cake_wallet/utils/responsive_layout_util.dart'; // import 'package:flutter/material.dart'; -// import 'package:ledger_flutter/ledger_flutter.dart'; +// import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; // import 'package:ledger_litecoin/ledger_litecoin.dart'; // import 'package:permission_handler/permission_handler.dart'; // diff --git a/lib/src/screens/connect_device/widgets/device_tile.dart b/lib/src/screens/connect_device/widgets/device_tile.dart index 8367d16067..58f65c5dee 100644 --- a/lib/src/screens/connect_device/widgets/device_tile.dart +++ b/lib/src/screens/connect_device/widgets/device_tile.dart @@ -1,6 +1,6 @@ import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:flutter/material.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; class DeviceTile extends StatelessWidget { const DeviceTile({ diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 97a7ad88de..95719c8413 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -68,11 +68,11 @@ class SendPage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.focusedChild?.unfocus(); + } + }; @override Widget? leading(BuildContext context) { @@ -81,8 +81,9 @@ class SendPage extends BasePage { color: titleColor(context), size: 16, ); - final _closeButton = - currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage; + final _closeButton = currentTheme.type == ThemeType.dark + ? closeButtonImageDarkTheme + : closeButtonImage; bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI; @@ -93,10 +94,13 @@ class SendPage extends BasePage { child: ButtonTheme( minWidth: double.minPositive, child: Semantics( - label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back, + label: !isMobileView + ? S.of(context).close + : S.of(context).seed_alert_back, child: TextButton( style: ButtonStyle( - overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent), + overlayColor: MaterialStateColor.resolveWith( + (states) => Colors.transparent), ), onPressed: () => onClose(context), child: !isMobileView ? _closeButton : _backButton, @@ -137,7 +141,8 @@ class SendPage extends BasePage { Padding( padding: const EdgeInsets.only(right: 8.0), child: Observer( - builder: (_) => SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend), + builder: (_) => + SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend), ), ), if (supMiddle != null) supMiddle @@ -171,10 +176,10 @@ class SendPage extends BasePage { _setEffects(context); return GestureDetector( - onLongPress: () => - sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing, - onLongPressUp: () => - sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing, + onLongPress: () => sendViewModel.balanceViewModel.isReversing = + !sendViewModel.balanceViewModel.isReversing, + onLongPressUp: () => sendViewModel.balanceViewModel.isReversing = + !sendViewModel.balanceViewModel.isReversing, child: Form( key: _formKey, child: ScrollableWithBottomSection( @@ -212,26 +217,25 @@ class SendPage extends BasePage { final count = sendViewModel.outputs.length; return count > 1 - ? Semantics ( - label: 'Page Indicator', - hint: 'Swipe to change receiver', - excludeSemantics: true, - child: - SmoothPageIndicator( - controller: controller, - count: count, - effect: ScrollingDotsEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context) - .extension()! - .indicatorDotColor, - activeDotColor: Theme.of(context) - .extension()! - .templateBackgroundColor), - )) + ? Semantics( + label: 'Page Indicator', + hint: 'Swipe to change receiver', + excludeSemantics: true, + child: SmoothPageIndicator( + controller: controller, + count: count, + effect: ScrollingDotsEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context) + .extension()! + .indicatorDotColor, + activeDotColor: Theme.of(context) + .extension()! + .templateBackgroundColor), + )) : Offstage(); }, ), @@ -251,7 +255,8 @@ class SendPage extends BasePage { return Row( children: [ AddTemplateButton( - onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate), + onTap: () => Navigator.of(context) + .pushNamed(Routes.sendTemplate), currentTemplatesLength: templates.length, ), ListView.builder( @@ -264,8 +269,11 @@ class SendPage extends BasePage { return TemplateTile( key: UniqueKey(), to: template.name, - hasMultipleRecipients: template.additionalRecipients != null && - template.additionalRecipients!.length > 1, + hasMultipleRecipients: + template.additionalRecipients != null && + template.additionalRecipients! + .length > + 1, amount: template.isCurrencySelected ? template.amount : template.amountFiat, @@ -273,11 +281,15 @@ class SendPage extends BasePage { ? template.cryptoCurrency : template.fiatCurrency, onTap: () async { - if (template.additionalRecipients?.isNotEmpty ?? false) { + if (template.additionalRecipients + ?.isNotEmpty ?? + false) { sendViewModel.clearOutputs(); for (int i = 0; - i < template.additionalRecipients!.length; + i < + template.additionalRecipients! + .length; i++) { Output output; try { @@ -290,7 +302,8 @@ class SendPage extends BasePage { await _setInputsFromTemplate( context, output: output, - template: template.additionalRecipients![i], + template: template + .additionalRecipients![i], ); } } else { @@ -307,17 +320,26 @@ class SendPage extends BasePage { context: context, builder: (dialogContext) { return AlertWithTwoActions( - alertTitle: S.of(context).template, - alertContent: S.of(context).confirm_delete_template, - rightButtonText: S.of(context).delete, - leftButtonText: S.of(context).cancel, + alertTitle: + S.of(context).template, + alertContent: S + .of(context) + .confirm_delete_template, + rightButtonText: + S.of(context).delete, + leftButtonText: + S.of(context).cancel, actionRightButton: () { - Navigator.of(dialogContext).pop(); - sendViewModel.sendTemplateViewModel - .removeTemplate(template: template); + Navigator.of(dialogContext) + .pop(); + sendViewModel + .sendTemplateViewModel + .removeTemplate( + template: template); }, actionLeftButton: () => - Navigator.of(dialogContext).pop()); + Navigator.of(dialogContext) + .pop()); }, ); }, @@ -333,7 +355,8 @@ class SendPage extends BasePage { ], ), ), - bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSectionPadding: + EdgeInsets.only(left: 24, right: 24, bottom: 24), bottomSection: Column( children: [ if (sendViewModel.hasCurrecyChanger) @@ -342,10 +365,12 @@ class SendPage extends BasePage { padding: EdgeInsets.only(bottom: 12), child: PrimaryButton( onPressed: () => presentCurrencyPicker(context), - text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})', + text: + 'Change your asset (${sendViewModel.selectedCryptoCurrency})', color: Colors.transparent, - textColor: - Theme.of(context).extension()!.hintTextColor, + textColor: Theme.of(context) + .extension()! + .hintTextColor, ))), if (sendViewModel.sendTemplateViewModel.hasMultiRecipient) Padding( @@ -354,21 +379,26 @@ class SendPage extends BasePage { onPressed: () { sendViewModel.addOutput(); Future.delayed(const Duration(milliseconds: 250), () { - controller.jumpToPage(sendViewModel.outputs.length - 1); + controller + .jumpToPage(sendViewModel.outputs.length - 1); }); }, text: S.of(context).add_receiver, color: Colors.transparent, - textColor: Theme.of(context).extension()!.hintTextColor, + textColor: Theme.of(context) + .extension()! + .hintTextColor, isDottedBorder: true, - borderColor: - Theme.of(context).extension()!.templateDottedBorderColor, + borderColor: Theme.of(context) + .extension()! + .templateDottedBorderColor, )), Observer( builder: (_) { return LoadingPrimaryButton( onPressed: () async { - if (_formKey.currentState != null && !_formKey.currentState!.validate()) { + if (_formKey.currentState != null && + !_formKey.currentState!.validate()) { if (sendViewModel.outputs.length > 1) { showErrorValidationAlert(context); } @@ -377,7 +407,9 @@ class SendPage extends BasePage { } final notValidItems = sendViewModel.outputs - .where((item) => item.address.isEmpty || item.cryptoAmount.isEmpty) + .where((item) => + item.address.isEmpty || + item.cryptoAmount.isEmpty) .toList(); if (notValidItems.isNotEmpty) { @@ -387,16 +419,19 @@ class SendPage extends BasePage { if (sendViewModel.wallet.isHardwareWallet) { if (!sendViewModel.ledgerViewModel!.isConnected) { - await Navigator.of(context).pushNamed(Routes.connectDevices, + await Navigator.of(context).pushNamed( + Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: sendViewModel.walletType, onConnectDevice: (BuildContext context, _) { - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet); + sendViewModel.ledgerViewModel! + .setLedger(sendViewModel.wallet); Navigator.of(context).pop(); }, )); } else { - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet); + sendViewModel.ledgerViewModel! + .setLedger(sendViewModel.wallet); } } @@ -466,14 +501,17 @@ class SendPage extends BasePage { return ConfirmSendingAlert( alertTitle: S.of(_dialogContext).confirm_sending, amount: S.of(_dialogContext).send_amount, - amountValue: sendViewModel.pendingTransaction!.amountFormatted, - fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted, + amountValue: + sendViewModel.pendingTransaction!.amountFormatted, + fiatAmountValue: + sendViewModel.pendingTransactionFiatAmountFormatted, fee: isEVMCompatibleChain(sendViewModel.walletType) ? S.of(_dialogContext).send_estimated_fee : S.of(_dialogContext).send_fee, feeRate: sendViewModel.pendingTransaction!.feeRate, feeValue: sendViewModel.pendingTransaction!.feeFormatted, - feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, + feeFiatAmount: sendViewModel + .pendingTransactionFeeFiatAmountFormatted, outputs: sendViewModel.outputs, rightButtonText: S.of(_dialogContext).send, leftButtonText: S.of(_dialogContext).cancel, @@ -491,17 +529,23 @@ class SendPage extends BasePage { } if (state is TransactionCommitted) { - newContactAddress = - newContactAddress ?? sendViewModel.newContactAddress(); - - final successMessage = S.of(_dialogContext).send_success( - sendViewModel.selectedCryptoCurrency.toString()); - - final waitMessage = sendViewModel.walletType == WalletType.solana + newContactAddress = newContactAddress ?? + sendViewModel.newContactAddress(); + + final successMessage = S + .of(_dialogContext) + .send_success(sendViewModel + .selectedCryptoCurrency + .toString()); + + final waitMessage = sendViewModel + .walletType == + WalletType.solana ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : ''; - final newContactMessage = newContactAddress != null + final newContactMessage = newContactAddress != + null ? '\n${S.of(_dialogContext).add_contact_to_address_book}' : ''; @@ -512,8 +556,10 @@ class SendPage extends BasePage { return AlertWithTwoActions( alertTitle: '', alertContent: alertContent, - rightButtonText: S.of(_dialogContext).add_contact, - leftButtonText: S.of(_dialogContext).ignor, + rightButtonText: + S.of(_dialogContext).add_contact, + leftButtonText: + S.of(_dialogContext).ignor, actionRightButton: () { Navigator.of(_dialogContext).pop(); RequestReviewHandler.requestReview(); @@ -528,9 +574,11 @@ class SendPage extends BasePage { newContactAddress = null; }); } else { - if (initialPaymentRequest?.callbackMessage?.isNotEmpty ?? + if (initialPaymentRequest + ?.callbackMessage?.isNotEmpty ?? false) { - alertContent = initialPaymentRequest!.callbackMessage!; + alertContent = initialPaymentRequest! + .callbackMessage!; } return AlertWithOneAction( alertTitle: '', @@ -547,7 +595,8 @@ class SendPage extends BasePage { }); }); if (state is TransactionCommitted) { - if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) { + if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? + false) { // wait a second so it's not as jarring: await Future.delayed(Duration(seconds: 1)); try { @@ -561,7 +610,8 @@ class SendPage extends BasePage { } } }, - actionLeftButton: () => Navigator.of(_dialogContext).pop()); + actionLeftButton: () => + Navigator.of(_dialogContext).pop()); }); } }); @@ -583,7 +633,10 @@ class SendPage extends BasePage { alertTitle: S.of(context).proceed_on_device, alertContent: S.of(context).proceed_on_device_description, buttonText: S.of(context).cancel, - buttonAction: () => Navigator.of(context).pop()); + buttonAction: () { + Navigator.of(context).pop(); + throw Exception("Please send the logs to Konsti"); + }); }); }); } @@ -600,8 +653,8 @@ class SendPage extends BasePage { sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency); output.setCryptoAmount(template.amount); } else { - final fiatFromTemplate = - FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); + final fiatFromTemplate = FiatCurrency.all + .singleWhere((element) => element.title == template.fiatCurrency); sendViewModel.setFiatCurrency(fiatFromTemplate); output.setFiatAmount(template.amountFiat); @@ -636,11 +689,12 @@ class SendPage extends BasePage { builder: (_) => Picker( items: sendViewModel.currencies, displayItem: (Object item) => item.toString(), - selectedAtIndex: - sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), + selectedAtIndex: sendViewModel.currencies + .indexOf(sendViewModel.selectedCryptoCurrency), title: S.of(context).please_select, mainAxisAlignment: MainAxisAlignment.center, - onItemSelected: (CryptoCurrency cur) => sendViewModel.selectedCryptoCurrency = cur, + onItemSelected: (CryptoCurrency cur) => + sendViewModel.selectedCryptoCurrency = cur, ), context: context); } diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 5e0c83f888..5335788c4d 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -17,7 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart'; class ExceptionHandler { static bool _hasError = false; - static const _coolDownDurationInDays = 7; + static const _coolDownDurationInDays = 0; static File? _file; static void _saveException(String? error, StackTrace? stackTrace, {String? library}) async { @@ -54,9 +54,9 @@ class ExceptionHandler { static void _sendExceptionFile() async { try { - if (_file == null) { - final appDocDir = await getAppDir(); + final appDocDir = await getAppDir(); + if (_file == null) { _file = File('${appDocDir.path}/error.txt'); } @@ -64,8 +64,8 @@ class ExceptionHandler { final MailOptions mailOptions = MailOptions( subject: 'Mobile App Issue', - recipients: ['support@cakewallet.com'], - attachments: [_file!.path], + recipients: ['konstantin@cakewallet.com'], + attachments: [_file!.path, '${appDocDir.path}/ledger_log.txt'], ); final result = await FlutterMailer.send(mailOptions); diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 156a587c0f..892f599a9d 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -9,11 +9,13 @@ import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/hardware/device_connection_type.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; + +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk; import 'package:permission_handler/permission_handler.dart'; class LedgerViewModel { - late final Ledger ledger; + // late final Ledger ledger; + late final sdk.LedgerInterface ledgerPlusBle; bool get _doesSupportHardwareWallets { if (!DeviceInfo.instance.isMobile) { @@ -21,7 +23,8 @@ class LedgerViewModel { } if (isMoneroOnly) { - return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS) + return DeviceConnectionType.supportedConnectionTypes( + WalletType.monero, Platform.isIOS) .isNotEmpty; } @@ -30,45 +33,41 @@ class LedgerViewModel { LedgerViewModel() { if (_doesSupportHardwareWallets) { - ledger = Ledger( - options: LedgerOptions( - scanMode: ScanMode.balanced, - maxScanDuration: const Duration(minutes: 5), - ), - onPermissionRequest: (_) async { - Map statuses = await [ - Permission.bluetoothScan, - Permission.bluetoothConnect, - Permission.bluetoothAdvertise, - ].request(); + ledgerPlusBle = sdk.LedgerInterface.ble(onPermissionRequest: (_) async { + Map statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.bluetoothAdvertise, + ].request(); - return statuses.values.where((status) => status.isDenied).isEmpty; - }, - ); + return statuses.values.where((status) => status.isDenied).isEmpty; + }); } } - Future connectLedger(LedgerDevice device) async { - await ledger.connect(device); + Stream scanForDevices() => ledgerPlusBle.scan(); - if (device.connectionType == ConnectionType.usb) _device = device; + Future connectLedger(sdk.LedgerDevice device) async { + if (isConnected) await _connection!.disconnect(); + _connection = await ledgerPlusBle.connect(device); + print("Connected"); } - LedgerDevice? _device; + sdk.LedgerConnection? _connection; - bool get isConnected => ledger.devices.isNotEmpty || _device != null; + bool get isConnected => _connection != null && !(_connection!.isDisconnected); - LedgerDevice get device => _device ?? ledger.devices.first; + sdk.LedgerConnection get connection => _connection!; void setLedger(WalletBase wallet) { switch (wallet.type) { case WalletType.bitcoin: case WalletType.litecoin: - return bitcoin!.setLedger(wallet, ledger, device); + return bitcoin!.setLedgerConnection(wallet, connection); case WalletType.ethereum: - return ethereum!.setLedger(wallet, ledger, device); + return ethereum!.setLedgerConnection(wallet, connection); case WalletType.polygon: - return polygon!.setLedger(wallet, ledger, device); + return polygon!.setLedgerConnection(wallet, connection); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index d0514bb190..eea2078558 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -22,7 +22,6 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/core/address_validator.dart'; @@ -377,16 +376,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor state = ExecutedSuccessfullyState(); return pendingTransaction; } catch (e) { - if (e is LedgerException) { - final errorCode = e.errorCode.toRadixString(16); - final fallbackMsg = - e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode"; - final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg; - - state = FailureState(errorMsg); - } else { + // if (e is LedgerException) { + // final errorCode = e.errorCode.toRadixString(16); + // final fallbackMsg = + // e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode"; + // final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg; + // + // state = FailureState(errorMsg); + // } else { state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); - } + // } } return null; } diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index 415f57d223..14c83b161c 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -12,7 +12,6 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:mobx/mobx.dart'; part 'wallet_hardware_restore_view_model.g.dart'; @@ -72,9 +71,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with availableAccounts.addAll(accounts); _nextIndex += limit; - } on LedgerException catch (e) { - error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16)); + // } on LedgerException catch (e) { + // error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16)); } catch (e) { + print(e); error = S.current.ledger_connection_error; } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 0b4ee9415c..7f5303fa23 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -16,6 +16,7 @@ import package_info_plus import path_provider_foundation import share_plus import shared_preferences_foundation +import universal_ble import url_launcher_macos import wakelock_plus @@ -31,6 +32,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UniversalBlePlugin.register(with: registry.registrar(forPlugin: "UniversalBlePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 567d1b210e..4d8d365262 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -12,7 +12,7 @@ dependencies: version: 4.0.2 shared_preferences: ^2.0.15 # provider: ^6.0.3 - rxdart: ^0.27.4 + rxdart: ^0.28.0 yaml: ^3.1.1 #barcode_scan: any barcode_scan2: ^4.2.1 @@ -102,6 +102,11 @@ dependencies: url: https://github.com/cake-tech/bitcoin_base ref: cake-update-v4 ledger_flutter: ^1.0.1 + ledger_flutter_plus: + git: + url: https://github.com/cake-tech/ledger-flutter-plus + ref: cake-v1 +# path: ../ledger-flutter-plus hashlib: 1.12.0 dev_dependencies: @@ -126,10 +131,6 @@ dependency_overrides: bech32: git: url: https://github.com/cake-tech/bech32.git - ledger_flutter: - git: - url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-v3 web3dart: git: url: https://github.com/cake-tech/web3dart.git diff --git a/scripts/android/inject_app_details.sh b/scripts/android/inject_app_details.sh index 2957b91e34..59420778d8 100755 --- a/scripts/android/inject_app_details.sh +++ b/scripts/android/inject_app_details.sh @@ -7,9 +7,9 @@ fi cd ../.. set -x -sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml -sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml -sed -i "0,/__APP_SCHEME__/s/__APP_SCHEME__/${APP_ANDROID_SCHEME}/" ./android/app/src/main/AndroidManifest.xml -sed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml -sed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml +gsed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml +gsed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml +gsed -i "0,/__APP_SCHEME__/s/__APP_SCHEME__/${APP_ANDROID_SCHEME}/" ./android/app/src/main/AndroidManifest.xml +gsed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml +gsed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml cd scripts/android diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c6444e09cc..ad540a3591 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UniversalBlePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UniversalBlePluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 0a0b2f9eb6..92431a6fb8 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows permission_handler_windows share_plus + universal_ble url_launcher_windows ) From 0f09991bac3159b4493c7bb1347fbbb4459eaa08 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 16 Sep 2024 14:34:16 +0200 Subject: [PATCH 13/19] ledger flutter plus refactoring --- cw_bitcoin/lib/electrum_wallet.dart | 2 +- cw_bitcoin/pubspec.lock | 130 ++++++++++++++++------------ tool/configure.dart | 12 +-- 3 files changed, 80 insertions(+), 64 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index e5f9ddc765..d6b41d2375 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -2175,7 +2175,7 @@ abstract class ElectrumWalletBase extends WalletBase< Timer(Duration(seconds: 10), () { if (this.syncStatus is NotConnectedSyncStatus || - this.syncStatus isLostConnectionSyncStatus) { + this.syncStatus is LostConnectionSyncStatus) { this.electrumClient.connectToUri( node!.uri, useSSL: node!.useSSL ?? false, diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 7a7da6d0c1..7649d2dc8e 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -84,6 +84,14 @@ packages: url: "https://github.com/cake-tech/blockchain_utils" source: git version: "3.3.0" + bluez: + dependency: transitive + description: + name: bluez + sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce" + url: "https://pub.dev" + source: hosted + version: "0.8.2" boolean_selector: dependency: transitive description: @@ -276,6 +284,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" encrypt: dependency: transitive description: @@ -337,19 +353,19 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1+1" - flutter_reactive_ble: - dependency: transitive - description: - name: flutter_reactive_ble - sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0" - url: "https://pub.dev" - source: hosted - version: "5.3.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_bluetooth: + dependency: transitive + description: + name: flutter_web_bluetooth + sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02" + url: "https://pub.dev" + source: hosted + version: "0.2.3" flutter_web_plugins: dependency: transitive description: flutter @@ -363,14 +379,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" - functional_data: - dependency: transitive - description: - name: functional_data - sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039" - url: "https://pub.dev" - source: hosted - version: "1.2.0" glob: dependency: transitive description: @@ -494,38 +502,38 @@ packages: ledger_bitcoin: dependency: "direct main" description: - path: "." + path: "packages/ledger-bitcoin" ref: HEAD - resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab - url: "https://github.com/cake-tech/ledger-bitcoin" + resolved-ref: "3bc008b863483f413ab70a411daab916fb570833" + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" source: git version: "0.0.2" - ledger_flutter: + ledger_flutter_plus: dependency: "direct main" description: path: "." - ref: cake-v3 - resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4" - url: "https://github.com/cake-tech/ledger-flutter.git" + ref: cake-v1 + resolved-ref: b95aa11489abf3c2c8ab65c6d874237e3bf23479 + url: "https://github.com/cake-tech/ledger-flutter-plus" source: git - version: "1.0.2" + version: "1.2.0" ledger_litecoin: dependency: "direct main" description: - path: "." + path: "packages/ledger-litecoin" ref: HEAD - resolved-ref: f2e9ef4d2b73f3408d2796e703ea158e1bedfe16 - url: "https://github.com/cake-tech/ledger-litecoin" + resolved-ref: "3bc008b863483f413ab70a411daab916fb570833" + url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" source: git version: "0.0.1" - ledger_usb: + ledger_usb_plus: dependency: transitive description: - name: ledger_usb - sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2" + name: ledger_usb_plus + sha256: f9f88253ca6d93559009b086ff45f0a4a0b34c1df477f653c59bc32562d16c65 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" logging: dependency: transitive description: @@ -654,6 +662,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -686,14 +702,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - protobuf: - dependency: transitive - description: - name: protobuf - sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" - url: "https://pub.dev" - source: hosted - version: "2.1.0" provider: dependency: transitive description: @@ -726,30 +734,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" - reactive_ble_mobile: - dependency: transitive - description: - name: reactive_ble_mobile - sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798" - url: "https://pub.dev" - source: hosted - version: "5.3.1" - reactive_ble_platform_interface: - dependency: transitive - description: - name: reactive_ble_platform_interface - sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813" - url: "https://pub.dev" - source: hosted - version: "5.3.1" rxdart: dependency: "direct main" description: name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "0.27.7" + version: "0.28.0" shared_preferences: dependency: "direct main" description: @@ -940,6 +932,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_ble: + dependency: transitive + description: + name: universal_ble + sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3" + url: "https://pub.dev" + source: hosted + version: "0.12.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" unorm_dart: dependency: transitive description: @@ -996,6 +1004,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: diff --git a/tool/configure.dart b/tool/configure.dart index 19f548408e..8e0bd8ae21 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -93,7 +93,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bip39/bip39.dart' as bip39; """; @@ -219,7 +219,7 @@ abstract class Bitcoin { Future updateFeeRates(Object wallet); int getMaxCustomFeeRate(Object wallet); - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); Future> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); } @@ -805,7 +805,7 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:web3dart/web3dart.dart'; """; @@ -871,7 +871,7 @@ abstract class Ethereum { Web3Client? getWeb3Client(WalletBase wallet); String getTokenAddress(CryptoCurrency asset); - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); } """; @@ -909,7 +909,7 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:web3dart/web3dart.dart'; """; @@ -975,7 +975,7 @@ abstract class Polygon { Web3Client? getWeb3Client(WalletBase wallet); String getTokenAddress(CryptoCurrency asset); - void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); } """; From 14a7100b3b1cdb846841b3266911cd5d7583f2c5 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 16 Sep 2024 14:41:56 +0200 Subject: [PATCH 14/19] ledger flutter plus refactoring --- pubspec_base.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec_base.yaml b/pubspec_base.yaml index f098c86d56..065b2f59df 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -101,7 +101,6 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_base ref: cake-update-v5 - ledger_flutter: ^1.0.1 ledger_flutter_plus: git: url: https://github.com/cake-tech/ledger-flutter-plus From 42f4edad3e8401c94b4bbbdf471cfdace73fad4e Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 16 Sep 2024 17:45:39 +0200 Subject: [PATCH 15/19] Ups :| --- scripts/android/inject_app_details.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/android/inject_app_details.sh b/scripts/android/inject_app_details.sh index 59420778d8..2957b91e34 100755 --- a/scripts/android/inject_app_details.sh +++ b/scripts/android/inject_app_details.sh @@ -7,9 +7,9 @@ fi cd ../.. set -x -gsed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml -gsed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml -gsed -i "0,/__APP_SCHEME__/s/__APP_SCHEME__/${APP_ANDROID_SCHEME}/" ./android/app/src/main/AndroidManifest.xml -gsed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml -gsed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml +sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml +sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml +sed -i "0,/__APP_SCHEME__/s/__APP_SCHEME__/${APP_ANDROID_SCHEME}/" ./android/app/src/main/AndroidManifest.xml +sed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml +sed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml cd scripts/android From 6b8237b66432e12915b5537ff5ab76fa7496ab8b Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 16 Sep 2024 19:53:59 +0200 Subject: [PATCH 16/19] Ups :| I forgot USB --- .../connect_device/connect_device_page.dart | 54 +++++++++++++------ .../hardware_wallet/ledger_view_model.dart | 18 +++++-- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index 48485b8e59..42c9566f6d 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -17,7 +17,8 @@ class ConnectDevicePageParams { final WalletType walletType; final OnConnectDevice onConnectDevice; - ConnectDevicePageParams({required this.walletType, required this.onConnectDevice}); + ConnectDevicePageParams( + {required this.walletType, required this.onConnectDevice}); } class ConnectDevicePage extends BasePage { @@ -33,7 +34,8 @@ class ConnectDevicePage extends BasePage { String get title => S.current.restore_title_from_hardware_wallet; @override - Widget body(BuildContext context) => ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM); + Widget body(BuildContext context) => + ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM); } class ConnectDevicePageBody extends StatefulWidget { @@ -41,7 +43,8 @@ class ConnectDevicePageBody extends StatefulWidget { final OnConnectDevice onConnectDevice; final LedgerViewModel ledgerVM; - const ConnectDevicePageBody(this.walletType, this.onConnectDevice, this.ledgerVM); + const ConnectDevicePageBody( + this.walletType, this.onConnectDevice, this.ledgerVM); @override ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState(); @@ -73,16 +76,18 @@ class ConnectDevicePageBodyState extends State { late Timer? _usbRefreshTimer = null; late Timer? _bleRefreshTimer = null; late StreamSubscription? _bleRefresh = null; + late StreamSubscription? _usbRefresh = null; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); + _bleRefreshTimer = + Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); - // if (Platform.isAndroid) { - // _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); - // } + if (Platform.isAndroid) { + _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); + } }); } @@ -91,17 +96,26 @@ class ConnectDevicePageBodyState extends State { _bleRefreshTimer?.cancel(); _usbRefreshTimer?.cancel(); _bleRefresh?.cancel(); + _usbRefresh?.cancel(); super.dispose(); } - // Future _refreshUsbDevices() async { - // final dev = await ledger.listUsbDevices(); - // if (usbDevices.length != dev.length) setState(() => usbDevices = dev); - // } + Future _refreshUsbDevices() async { + _usbRefresh = widget.ledgerVM + .scanForUsbDevices() + .listen((device) => setState(() => usbDevices.add(device))) + ..onError((e) { + throw e.toString(); + }); + _usbRefreshTimer?.cancel(); + _usbRefreshTimer = null; + } Future _refreshBleDevices() async { try { - _bleRefresh = widget.ledgerVM.scanForDevices().listen((device) => setState(() => bleDevices.add(device))) + _bleRefresh = widget.ledgerVM + .scanForBleDevices() + .listen((device) => setState(() => bleDevices.add(device))) ..onError((e) { throw e.toString(); }); @@ -137,7 +151,9 @@ class ConnectDevicePageBodyState extends State { style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.titleColor), + color: Theme.of(context) + .extension()! + .titleColor), textAlign: TextAlign.center, ), ), @@ -158,7 +174,9 @@ class ConnectDevicePageBodyState extends State { style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.titleColor), + color: Theme.of(context) + .extension()! + .titleColor), textAlign: TextAlign.center, ), ), @@ -172,7 +190,9 @@ class ConnectDevicePageBodyState extends State { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.titleColor, + color: Theme.of(context) + .extension()! + .titleColor, ), ), ), @@ -201,7 +221,9 @@ class ConnectDevicePageBodyState extends State { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, - color: Theme.of(context).extension()!.titleColor, + color: Theme.of(context) + .extension()! + .titleColor, ), ), ), diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 892f599a9d..da95ead74b 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -15,7 +15,8 @@ import 'package:permission_handler/permission_handler.dart'; class LedgerViewModel { // late final Ledger ledger; - late final sdk.LedgerInterface ledgerPlusBle; + late final sdk.LedgerInterface ledgerPlusBLE; + late final sdk.LedgerInterface ledgerPlusUSB; bool get _doesSupportHardwareWallets { if (!DeviceInfo.instance.isMobile) { @@ -33,7 +34,7 @@ class LedgerViewModel { LedgerViewModel() { if (_doesSupportHardwareWallets) { - ledgerPlusBle = sdk.LedgerInterface.ble(onPermissionRequest: (_) async { + ledgerPlusBLE = sdk.LedgerInterface.ble(onPermissionRequest: (_) async { Map statuses = await [ Permission.bluetoothScan, Permission.bluetoothConnect, @@ -42,14 +43,23 @@ class LedgerViewModel { return statuses.values.where((status) => status.isDenied).isEmpty; }); + + if (!Platform.isIOS) { + ledgerPlusUSB = sdk.LedgerInterface.usb(); + } } } - Stream scanForDevices() => ledgerPlusBle.scan(); + Stream scanForBleDevices() => ledgerPlusBLE.scan(); + + Stream scanForUsbDevices() => ledgerPlusUSB.scan(); Future connectLedger(sdk.LedgerDevice device) async { if (isConnected) await _connection!.disconnect(); - _connection = await ledgerPlusBle.connect(device); + final ledger = device.connectionType == sdk.ConnectionType.ble + ? ledgerPlusBLE + : ledgerPlusUSB; + _connection = await ledger.connect(device); print("Connected"); } From 1e91aa8f65abb787be8b11296c2d42f5e9d61ed4 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 17 Sep 2024 14:34:03 +0200 Subject: [PATCH 17/19] Upgrading to Ledger Flutter Plus --- .../bitcoin_cash_hardware_wallet_service.dart | 28 +++++++++---------- .../lib/src/bitcoin_cash_wallet.dart | 13 ++++----- cw_bitcoin_cash/pubspec.yaml | 8 ++++-- tool/configure.dart | 1 - 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart index 59e4ae3350..cfd55c5653 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_hardware_wallet_service.dart @@ -4,19 +4,19 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; class BitcoinCashHardwareWalletService { - BitcoinCashHardwareWalletService(this.ledger, this.device); + BitcoinCashHardwareWalletService(this.ledgerConnection); - final Ledger ledger; - final LedgerDevice device; + final LedgerConnection ledgerConnection; - Future> getAvailableAccounts({int index = 0, int limit = 5}) async { - final bitcoinCashLedgerApp = LitecoinLedgerApp(ledger); + Future> getAvailableAccounts( + {int index = 0, int limit = 5}) async { + final bitcoinCashLedgerApp = LitecoinLedgerApp(ledgerConnection); - final version = await bitcoinCashLedgerApp.getVersion(device); + final version = await bitcoinCashLedgerApp.getVersion(); print(version); final accounts = []; @@ -26,24 +26,24 @@ class BitcoinCashHardwareWalletService { for (final i in indexRange) { final derivationPath = "m/44'/145'/$i'"; final xpub = await bitcoinCashLedgerApp.getXPubKey( - device, accountsDerivationPath: derivationPath, xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16), addressFormat: AddressFormat.cashaddr, ); - final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0)); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion) + .childKey(Bip32KeyIndex(0)); - final address = generateP2PKHAddress(hd: hd, index: 0, network: BitcoinCashNetwork.mainnet); + final address = generateP2PKHAddress( + hd: hd, index: 0, network: BitcoinCashNetwork.mainnet); - accounts.add(HardwareAccountData( + accounts.add(HardwareAccountData( address: address, accountIndex: i, derivationPath: derivationPath, xpub: xpub, )); - } + } - return - accounts; + return accounts; } } diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 5f836b6db1..abb289c878 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -15,7 +15,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; import 'package:mobx/mobx.dart'; @@ -227,16 +227,14 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { return priv.signMessage(StringUtils.encode(message)); } - Ledger? _ledger; - LedgerDevice? _ledgerDevice; + LedgerConnection? _ledgerConnection; LitecoinLedgerApp? _bitcoinCashLedgerApp; @override - void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { - _ledger = setLedger; - _ledgerDevice = setLedgerDevice; + void setLedgerConnection(LedgerConnection connection) { + _ledgerConnection = connection; _bitcoinCashLedgerApp = - LitecoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); + LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!); } @override @@ -274,7 +272,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { final rawHex = await _bitcoinCashLedgerApp!.createTransaction( - _ledgerDevice!, inputs: readyInputs, outputs: outputs .map((e) => TransactionOutput.fromBigInt( diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index ef86f8b361..f661467dbd 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -29,10 +29,14 @@ dependencies: git: url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v2 - ledger_flutter: ^1.0.1 + ledger_flutter_plus: + git: + url: https://github.com/cake-tech/ledger-flutter-plus + ref: cake-v1 ledger_litecoin: git: - url: https://github.com/cake-tech/ledger-litecoin + url: https://github.com/cake-tech/ledger-flutter-plus-plugins + path: ./packages/ledger-litecoin dev_dependencies: flutter_test: diff --git a/tool/configure.dart b/tool/configure.dart index ce3e6bfa68..421bfedf57 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1011,7 +1011,6 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter/ledger_flutter.dart'; """; const bitcoinCashCWHeaders = """ import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; From e9d5da01a2055b13be363999eb8ec9cfac2fcabc Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 17 Sep 2024 14:42:35 +0200 Subject: [PATCH 18/19] Upgrading to Ledger Flutter Plus --- lib/bitcoin_cash/cw_bitcoin_cash.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart index f9c7ae027d..f06a23a903 100644 --- a/lib/bitcoin_cash/cw_bitcoin_cash.dart +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -49,7 +49,7 @@ class CWBitcoinCash extends BitcoinCash { @override Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { - final hardwareWalletService = BitcoinCashHardwareWalletService(ledgerVM.ledger, ledgerVM.device); + final hardwareWalletService = BitcoinCashHardwareWalletService(ledgerVM.connection); try { return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); } on LedgerException catch (err) { From 25a2cb194dd73d8c6357a558332b9a6ef35270d6 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 17 Sep 2024 14:58:30 +0200 Subject: [PATCH 19/19] Upgrading to Ledger Flutter Plus --- lib/bitcoin_cash/cw_bitcoin_cash.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart index f06a23a903..a86fdca868 100644 --- a/lib/bitcoin_cash/cw_bitcoin_cash.dart +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -52,8 +52,8 @@ class CWBitcoinCash extends BitcoinCash { final hardwareWalletService = BitcoinCashHardwareWalletService(ledgerVM.connection); try { return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); - } on LedgerException catch (err) { - print(err.message); + } catch (err) { + print(err); throw err; } }