diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml
index dc231df42..69c632967 100644
--- a/.github/workflows/pr_test_build.yml
+++ b/.github/workflows/pr_test_build.yml
@@ -42,7 +42,7 @@ jobs:
- name: Flutter action
uses: subosito/flutter-action@v1
with:
- flutter-version: "3.10.x"
+ flutter-version: "3.19.5"
channel: stable
- name: Install package dependencies
@@ -113,6 +113,7 @@ jobs:
touch lib/.secrets.g.dart
touch cw_evm/lib/.secrets.g.dart
touch cw_solana/lib/.secrets.g.dart
+ touch cw_tron/lib/.secrets.g.dart
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
@@ -150,6 +151,8 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
+ echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
+ echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
- name: Rename app
run: |
diff --git a/.gitignore b/.gitignore
index 6f2d0a182..d0952ca98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,9 +94,12 @@ android/app/key.jks
**/tool/.evm-secrets-config.json
**/tool/.ethereum-secrets-config.json
**/tool/.solana-secrets-config.json
+**/tool/.nano-secrets-config.json
+**/tool/.tron-secrets-config.json
**/lib/.secrets.g.dart
**/cw_evm/lib/.secrets.g.dart
**/cw_solana/lib/.secrets.g.dart
+**/cw_tron/lib/.secrets.g.dart
vendor/
@@ -132,6 +135,7 @@ lib/bitcoin_cash/bitcoin_cash.dart
lib/nano/nano.dart
lib/polygon/polygon.dart
lib/solana/solana.dart
+lib/tron/tron.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
@@ -152,6 +156,7 @@ assets/images/app_logo.png
macos/Runner/Info.plist
macos/Runner/DebugProfile.entitlements
macos/Runner/Release.entitlements
+lib/core/secure_storage.dart
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index eea9b5521..57462099c 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -9,6 +9,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final bitcoinLedgerApp = BitcoinLedgerApp(ledger);
+
+ final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device);
+ print(masterFp);
+
+ 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);
+ HDWallet hd = HDWallet.fromBase58(xpub).derive(0);
+
+ final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
+
+ accounts.add(HardwareAccountData(
+ address: address,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ masterFingerprint: masterFp,
+ xpub: xpub,
+ ));
+ }
+
+ return accounts;
+ }
+}
diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart
index 5ccc3ef33..f96b0e4da 100644
--- a/cw_bitcoin/lib/bitcoin_wallet.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet.dart
@@ -1,8 +1,13 @@
import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:convert/convert.dart';
+
import 'package:cw_bitcoin/bitcoin_mnemonic.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:hive/hive.dart';
+import 'package:ledger_bitcoin/ledger_bitcoin.dart';
+import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
@@ -20,11 +25,12 @@ class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinWalletBase({
- required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box unspentCoinsInfo,
- required Uint8List seedBytes,
+ Uint8List? seedBytes,
+ String? mnemonic,
+ String? xpub,
String? addressPageType,
BasedUtxoNetwork? networkParam,
List? initialAddresses,
@@ -33,32 +39,34 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map? initialChangeAddressIndex,
String? passphrase,
}) : super(
- mnemonic: mnemonic,
- passphrase: passphrase,
- password: password,
- walletInfo: walletInfo,
- unspentCoinsInfo: unspentCoinsInfo,
- networkType: networkParam == null
+ mnemonic: mnemonic,
+ passphrase: passphrase,
+ xpub: xpub,
+ password: password,
+ walletInfo: walletInfo,
+ unspentCoinsInfo: unspentCoinsInfo,
+ networkType: networkParam == null
+ ? bitcoin.bitcoin
+ : networkParam == BitcoinNetwork.mainnet
? bitcoin.bitcoin
- : networkParam == BitcoinNetwork.mainnet
- ? bitcoin.bitcoin
- : bitcoin.testnet,
- initialAddresses: initialAddresses,
- initialBalance: initialBalance,
- seedBytes: seedBytes,
- currency: CryptoCurrency.btc,
- ) {
- String derivationPath = walletInfo.derivationInfo!.derivationPath! + "/0";
- String sideDerivationPath = walletInfo.derivationInfo!.derivationPath! + "/1";
- final hd2 = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
+ : bitcoin.testnet,
+ initialAddresses: initialAddresses,
+ initialBalance: initialBalance,
+ seedBytes: seedBytes,
+ currency: CryptoCurrency.btc) {
+ // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
+ // the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
+ // String derivationPath = walletInfo.derivationInfo!.derivationPath!;
+ // String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
+ // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
walletAddresses = BitcoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
- mainHd: hd2.derivePath(derivationPath),
- sideHd: hd2.derivePath(sideDerivationPath),
+ mainHd: hd,
+ sideHd: accountHD.derive(1),
network: networkParam ?? network,
);
autorun((_) {
@@ -126,25 +134,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
);
// set the default if not present:
- walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'";
+ walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/0";
+ walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
- late Uint8List seedBytes;
+ Uint8List? seedBytes = null;
- switch (walletInfo.derivationInfo!.derivationType) {
- case DerivationType.electrum:
- seedBytes = await mnemonicToSeedBytes(snp.mnemonic);
- break;
- case DerivationType.bip39:
- default:
- seedBytes = await bip39.mnemonicToSeed(
- snp.mnemonic,
- passphrase: snp.passphrase ?? '',
- );
- break;
+ if (snp.mnemonic != null) {
+ switch (walletInfo.derivationInfo!.derivationType) {
+ case DerivationType.electrum:
+ seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
+ break;
+ case DerivationType.bip39:
+ default:
+ seedBytes = await bip39.mnemonicToSeed(
+ snp.mnemonic!,
+ passphrase: snp.passphrase ?? '',
+ );
+ break;
+ }
}
return BitcoinWallet(
mnemonic: snp.mnemonic,
+ xpub: snp.xpub,
password: password,
passphrase: snp.passphrase,
walletInfo: walletInfo,
@@ -158,4 +170,49 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
networkParam: network,
);
}
+
+ Ledger? _ledger;
+ LedgerDevice? _ledgerDevice;
+ BitcoinLedgerApp? _bitcoinLedgerApp;
+
+ void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
+ _ledger = setLedger;
+ _ledgerDevice = setLedgerDevice;
+ _bitcoinLedgerApp = BitcoinLedgerApp(_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 masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!);
+
+ final psbtReadyInputs = [];
+ for (final utxo in utxos) {
+ final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
+ final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
+
+ psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
+ utxo: utxo.utxo,
+ rawTx: rawTx,
+ ownerDetails: utxo.ownerDetails,
+ ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
+ ownerMasterFingerprint: masterFingerprint,
+ ownerPublicKey: publicKeyAndDerivationPath.publicKey,
+ ));
+ }
+
+ final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
+
+ final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
+ return BtcTransaction.fromRaw(hex.encode(rawHex));
+ }
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
index 981c7a466..915d7cc10 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
+++ b/cw_bitcoin/lib/bitcoin_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';
@@ -36,9 +37,22 @@ class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
}
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
- BitcoinRestoreWalletFromWIFCredentials(
- {required String name, required String password, required this.wif, WalletInfo? walletInfo})
- : super(name: name, password: password, walletInfo: walletInfo);
+ BitcoinRestoreWalletFromWIFCredentials({
+ required String name,
+ required String password,
+ required this.wif,
+ WalletInfo? walletInfo,
+ }) : super(name: name, password: password, walletInfo: walletInfo);
final String wif;
}
+
+class BitcoinRestoreWalletFromHardware extends WalletCredentials {
+ BitcoinRestoreWalletFromHardware({
+ required String name,
+ required this.hwAccountData,
+ WalletInfo? walletInfo,
+ }) : super(name: name, walletInfo: walletInfo);
+
+ final HardwareAccountData hwAccountData;
+}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart
index e0548771b..cf99324da 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_service.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart
@@ -14,8 +14,11 @@ import 'package:hive/hive.dart';
import 'package:collection/collection.dart';
import 'package:bip39/bip39.dart' as bip39;
-class BitcoinWalletService extends WalletService {
+class BitcoinWalletService extends WalletService<
+ BitcoinNewWalletCredentials,
+ BitcoinRestoreWalletFromSeedCredentials,
+ BitcoinRestoreWalletFromWIFCredentials,
+ BitcoinRestoreWalletFromHardware> {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box walletInfoSource;
@@ -99,9 +102,28 @@ class BitcoinWalletService extends WalletService restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
+ {bool? isTestnet}) async {
+
+ final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
+ credentials.walletInfo?.network = network.value;
+ credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath;
+
+ final wallet = await BitcoinWallet(password: credentials.password!,
+ xpub: credentials.hwAccountData.xpub,
+ walletInfo: credentials.walletInfo!,
+ unspentCoinsInfo: unspentCoinsInfoSource,
+ networkParam: network,
+ );
+ await wallet.save();
+ await wallet.init();
+ return wallet;
+ }
+
@override
Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
- {bool? isTestnet}) async =>
+ {bool? isTestnet}) async =>
throw UnimplementedError();
@override
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index b184cbb6a..783eb10d7 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -4,8 +4,8 @@ import 'dart:io';
import 'dart:math';
import 'package:bitcoin_base/bitcoin_base.dart';
-import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
+import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
@@ -37,9 +37,9 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
+import 'package:http/http.dart' as http;
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
-import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart';
@@ -53,17 +53,16 @@ abstract class ElectrumWalletBase
required WalletInfo walletInfo,
required Box unspentCoinsInfo,
required this.networkType,
- required this.mnemonic,
- required Uint8List seedBytes,
+ String? xpub,
+ String? mnemonic,
+ Uint8List? seedBytes,
this.passphrase,
List? initialAddresses,
ElectrumClient? electrumClient,
ElectrumBalance? initialBalance,
CryptoCurrency? currency})
- : hd = currency == CryptoCurrency.bch
- ? bitcoinCashHDWallet(seedBytes)
- : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
- .derivePath(walletInfo.derivationInfo?.derivationPath ?? "m/0'"),
+ : accountHD =
+ getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = [],
@@ -80,20 +79,44 @@ abstract class ElectrumWalletBase
this.unspentCoinsInfo = unspentCoinsInfo,
this.network = _getNetwork(networkType, currency),
this.isTestnet = networkType == bitcoin.testnet,
+ this._mnemonic = mnemonic,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo;
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
}
+ static bitcoin.HDWallet getAccountHDWallet(
+ CryptoCurrency? currency,
+ bitcoin.NetworkType networkType,
+ 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");
+ }
+
+ if (seedBytes != null) {
+ return currency == CryptoCurrency.bch
+ ? bitcoinCashHDWallet(seedBytes)
+ : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
+ .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? "m/0'"));
+ }
+
+ return bitcoin.HDWallet.fromBase58(xpub!);
+ }
+
static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
- bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/0");
+ bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'");
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10;
- final bitcoin.HDWallet hd;
- final String mnemonic;
+ final bitcoin.HDWallet accountHD;
+ final String? _mnemonic;
+
+ bitcoin.HDWallet get hd => accountHD.derive(0);
final String? passphrase;
@override
@@ -123,10 +146,10 @@ abstract class ElectrumWalletBase
.map((addr) => scriptHash(addr.address, network: network))
.toList();
- String get xpub => hd.base58!;
+ String get xpub => accountHD.base58!;
@override
- String get seed => mnemonic;
+ String? get seed => _mnemonic;
bitcoin.NetworkType networkType;
BasedUtxoNetwork network;
@@ -203,7 +226,9 @@ abstract class ElectrumWalletBase
int credentialsAmount = 0,
}) async {
final utxos = [];
- List privateKeys = [];
+ final privateKeys = [];
+ final publicKeys = {};
+
int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
@@ -217,12 +242,22 @@ abstract class ElectrumWalletBase
allInputsAmount += utx.value;
final address = addressTypeFromStr(utx.address, network);
- final privkey = generateECPrivate(
- hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
- index: utx.bitcoinAddressRecord.index,
- network: network);
+ final hd =
+ utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
+ final derivationPath =
+ "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
+ "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
+ "/${utx.bitcoinAddressRecord.index}";
+ final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
- privateKeys.add(privkey);
+ publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
+
+ if (!walletInfo.isHardwareWallet) {
+ final privkey =
+ generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
+
+ privateKeys.add(privkey);
+ }
utxos.add(
UtxoWithAddress(
@@ -233,7 +268,7 @@ abstract class ElectrumWalletBase
scriptType: _getScriptType(address),
),
ownerDetails: UtxoAddressDetails(
- publicKey: privkey.getPublic().toHex(),
+ publicKey: pubKeyHex,
address: address,
),
),
@@ -294,6 +329,7 @@ abstract class ElectrumWalletBase
return EstimatedTxResult(
utxos: utxos,
privateKeys: privateKeys,
+ publicKeys: publicKeys,
fee: fee,
amount: amount,
isSendAll: true,
@@ -312,7 +348,9 @@ abstract class ElectrumWalletBase
bool? useUnconfirmed,
}) async {
final utxos = [];
- List privateKeys = [];
+ final privateKeys = [];
+ final publicKeys = {};
+
int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
@@ -332,12 +370,23 @@ abstract class ElectrumWalletBase
leftAmount = leftAmount - utx.value;
final address = addressTypeFromStr(utx.address, network);
- final privkey = generateECPrivate(
- hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
- index: utx.bitcoinAddressRecord.index,
- network: network);
- privateKeys.add(privkey);
+ final hd =
+ utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
+ final derivationPath =
+ "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
+ "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
+ "/${utx.bitcoinAddressRecord.index}";
+ final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
+
+ publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
+
+ if (!walletInfo.isHardwareWallet) {
+ final privkey =
+ generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
+
+ privateKeys.add(privkey);
+ }
utxos.add(
UtxoWithAddress(
@@ -348,7 +397,7 @@ abstract class ElectrumWalletBase
scriptType: _getScriptType(address),
),
ownerDetails: UtxoAddressDetails(
- publicKey: privkey.getPublic().toHex(),
+ publicKey: pubKeyHex,
address: address,
),
),
@@ -490,6 +539,7 @@ abstract class ElectrumWalletBase
return EstimatedTxResult(
utxos: utxos,
privateKeys: privateKeys,
+ publicKeys: publicKeys,
fee: fee,
amount: amount,
hasChange: true,
@@ -557,6 +607,35 @@ abstract class ElectrumWalletBase
);
}
+ if (walletInfo.isHardwareWallet) {
+ final transaction = await buildHardwareWalletTransaction(
+ utxos: estimatedTx.utxos,
+ outputs: outputs,
+ publicKeys: estimatedTx.publicKeys,
+ fee: BigInt.from(estimatedTx.fee),
+ network: network,
+ memo: estimatedTx.memo,
+ outputOrdering: BitcoinOrdering.none,
+ enableRBF: true,
+ );
+
+ return PendingBitcoinTransaction(
+ transaction,
+ type,
+ electrumClient: electrumClient,
+ amount: estimatedTx.amount,
+ fee: estimatedTx.fee,
+ feeRate: feeRateInt.toString(),
+ network: network,
+ hasChange: estimatedTx.hasChange,
+ isSendAll: estimatedTx.isSendAll,
+ hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot
+ )..addListener((transaction) async {
+ transactionHistory.addOne(transaction);
+ await updateBalance();
+ });
+ }
+
BasedBitcoinTransacationBuilder txb;
if (network is BitcoinCashNetwork) {
txb = ForkedTransactionBuilder(
@@ -618,8 +697,22 @@ abstract class ElectrumWalletBase
}
}
+ 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 =>
+ throw UnimplementedError();
+
String toJSON() => json.encode({
- 'mnemonic': mnemonic,
+ 'mnemonic': _mnemonic,
+ 'xpub': xpub,
'passphrase': passphrase ?? '',
'account_index': walletAddresses.currentReceiveAddressIndexByType,
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
@@ -1263,7 +1356,7 @@ abstract class ElectrumWalletBase
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
@override
- String signMessage(String message, {String? address = null}) {
+ Future signMessage(String message, {String? address = null}) async {
final index = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
: null;
@@ -1286,6 +1379,9 @@ abstract class ElectrumWalletBase
return BitcoinNetwork.mainnet;
}
+
+ static String _hardenedDerivationPath(String derivationPath) =>
+ derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
}
class EstimateTxParams {
@@ -1307,6 +1403,7 @@ class EstimatedTxResult {
EstimatedTxResult({
required this.utxos,
required this.privateKeys,
+ required this.publicKeys,
required this.fee,
required this.amount,
required this.hasChange,
@@ -1317,6 +1414,7 @@ class EstimatedTxResult {
final List utxos;
final List privateKeys;
+ final Map publicKeys; // PubKey to derivationPath
final int fee;
final int amount;
final bool hasChange;
@@ -1325,6 +1423,13 @@ class EstimatedTxResult {
final bool spendsUnconfirmedTX;
}
+class PublicKeyWithDerivationPath {
+ const PublicKeyWithDerivationPath(this.publicKey, this.derivationPath);
+
+ final String derivationPath;
+ final String publicKey;
+}
+
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
if (network is BitcoinCashNetwork) {
if (!address.startsWith("bitcoincash:") &&
diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart
index 6cbd70314..0b469eab7 100644
--- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart
+++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart
@@ -13,6 +13,7 @@ class ElectrumWalletSnapshot {
required this.type,
required this.password,
required this.mnemonic,
+ required this.xpub,
required this.addresses,
required this.balance,
required this.regularAddressIndex,
@@ -28,7 +29,8 @@ class ElectrumWalletSnapshot {
final WalletType type;
final String? addressPageType;
- String mnemonic;
+ String? mnemonic;
+ String? xpub;
List addresses;
ElectrumBalance balance;
Map regularAddressIndex;
@@ -43,7 +45,8 @@ class ElectrumWalletSnapshot {
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final addressesTmp = data['addresses'] as List? ??