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 435c94681..060139668 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
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 ae980b87a..05ba82da0 100644
--- a/cw_bitcoin/lib/bitcoin_wallet.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet.dart
@@ -1,9 +1,14 @@
import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:convert/convert.dart';
+
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_core/encryption_file_utils.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;
@@ -21,12 +26,13 @@ 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,
required EncryptionFileUtils encryptionFileUtils,
+ Uint8List? seedBytes,
+ String? mnemonic,
+ String? xpub,
String? addressPageType,
BasedUtxoNetwork? networkParam,
List? initialAddresses,
@@ -35,25 +41,28 @@ 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
- ? bitcoin.bitcoin
- : networkParam == BitcoinNetwork.mainnet
- ? bitcoin.bitcoin
- : bitcoin.testnet,
- initialAddresses: initialAddresses,
- initialBalance: initialBalance,
- seedBytes: seedBytes,
- currency: CryptoCurrency.btc,
- encryptionFileUtils: encryptionFileUtils) {
+ mnemonic: mnemonic,
+ passphrase: passphrase,
+ xpub: xpub,
+ password: password,
+ walletInfo: walletInfo,
+ unspentCoinsInfo: unspentCoinsInfo,
+ networkType: networkParam == null
+ ? bitcoin.bitcoin
+ : networkParam == BitcoinNetwork.mainnet
+ ? bitcoin.bitcoin
+ : bitcoin.testnet,
+ initialAddresses: initialAddresses,
+ initialBalance: initialBalance,
+ seedBytes: seedBytes,
+ currency: CryptoCurrency.btc,
+ encryptionFileUtils: encryptionFileUtils,
+ ) {
// 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";
+ // 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,
@@ -61,7 +70,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
- sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath(sideDerivationPath),
+ sideHd: accountHD.derive(1),
network: networkParam ?? network,
);
autorun((_) {
@@ -135,23 +144,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
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,
@@ -166,4 +178,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 38618c6d1..91b8e4ae2 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';
@@ -38,9 +39,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 d4c36cafb..e9602b506 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_service.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart
@@ -15,8 +15,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, this.isDirect);
final Box walletInfoSource;
@@ -105,9 +108,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_derivations.dart b/cw_bitcoin/lib/electrum_derivations.dart
index 631805c54..19d444a41 100644
--- a/cw_bitcoin/lib/electrum_derivations.dart
+++ b/cw_bitcoin/lib/electrum_derivations.dart
@@ -4,7 +4,7 @@ Map> electrum_derivations = {
DerivationType.electrum: [
DerivationInfo(
derivationType: DerivationType.electrum,
- derivationPath: "m/0'/0",
+ derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
),
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 7d1996863..1348fd0e6 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -38,9 +38,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';
@@ -51,21 +51,20 @@ abstract class ElectrumWalletBase
with Store {
ElectrumWalletBase(
{required String password,
- required WalletInfo walletInfo,
- required Box unspentCoinsInfo,
- required this.networkType,
- required this.mnemonic,
- required Uint8List seedBytes,
- required this.encryptionFileUtils,
- 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'/0"),
+ required WalletInfo walletInfo,
+ required Box unspentCoinsInfo,
+ required this.networkType,
+ required this.encryptionFileUtils,
+ String? xpub,
+ String? mnemonic,
+ Uint8List? seedBytes,
+ this.passphrase,
+ List? initialAddresses,
+ ElectrumClient? electrumClient,
+ ElectrumBalance? initialBalance,
+ CryptoCurrency? currency})
+ : accountHD =
+ getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = [],
@@ -82,6 +81,7 @@ 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;
@@ -92,14 +92,38 @@ abstract class ElectrumWalletBase
encryptionFileUtils: encryptionFileUtils);
}
+ 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 EncryptionFileUtils encryptionFileUtils;
final String? passphrase;
@@ -130,10 +154,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;
@override
String get password => _password;
@@ -213,7 +237,9 @@ abstract class ElectrumWalletBase
int credentialsAmount = 0,
}) async {
final utxos = [];
- List privateKeys = [];
+ final privateKeys = [];
+ final publicKeys = {};
+
int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
@@ -227,12 +253,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(
@@ -243,7 +279,7 @@ abstract class ElectrumWalletBase
scriptType: _getScriptType(address),
),
ownerDetails: UtxoAddressDetails(
- publicKey: privkey.getPublic().toHex(),
+ publicKey: pubKeyHex,
address: address,
),
),
@@ -304,6 +340,7 @@ abstract class ElectrumWalletBase
return EstimatedTxResult(
utxos: utxos,
privateKeys: privateKeys,
+ publicKeys: publicKeys,
fee: fee,
amount: amount,
isSendAll: true,
@@ -322,7 +359,9 @@ abstract class ElectrumWalletBase
bool? useUnconfirmed,
}) async {
final utxos = [];
- List privateKeys = [];
+ final privateKeys = [];
+ final publicKeys = {};
+
int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
@@ -342,12 +381,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(
@@ -358,7 +408,7 @@ abstract class ElectrumWalletBase
scriptType: _getScriptType(address),
),
ownerDetails: UtxoAddressDetails(
- publicKey: privkey.getPublic().toHex(),
+ publicKey: pubKeyHex,
address: address,
),
),
@@ -500,6 +550,7 @@ abstract class ElectrumWalletBase
return EstimatedTxResult(
utxos: utxos,
privateKeys: privateKeys,
+ publicKeys: publicKeys,
fee: fee,
amount: amount,
hasChange: true,
@@ -567,6 +618,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(
@@ -628,8 +708,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,
@@ -1273,7 +1367,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;
@@ -1296,6 +1390,9 @@ abstract class ElectrumWalletBase
return BitcoinNetwork.mainnet;
}
+
+ static String _hardenedDerivationPath(String derivationPath) =>
+ derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
}
class EstimateTxParams {
@@ -1317,6 +1414,7 @@ class EstimatedTxResult {
EstimatedTxResult({
required this.utxos,
required this.privateKeys,
+ required this.publicKeys,
required this.fee,
required this.amount,
required this.hasChange,
@@ -1327,6 +1425,7 @@ class EstimatedTxResult {
final List utxos;
final List privateKeys;
+ final Map publicKeys; // PubKey to derivationPath
final int fee;
final int amount;
final bool hasChange;
@@ -1335,6 +1434,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 43bf5ab28..3eb15fe94 100644
--- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart
+++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart
@@ -14,6 +14,7 @@ class ElectrumWalletSnapshot {
required this.type,
required this.password,
required this.mnemonic,
+ required this.xpub,
required this.addresses,
required this.balance,
required this.regularAddressIndex,
@@ -29,7 +30,8 @@ class ElectrumWalletSnapshot {
final WalletType type;
final String? addressPageType;
- String mnemonic;
+ String? mnemonic;
+ String? xpub;
List addresses;
ElectrumBalance balance;
Map regularAddressIndex;
@@ -44,7 +46,8 @@ class ElectrumWalletSnapshot {
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final addressesTmp = data['addresses'] as List? ??