From 628fb212fb4151d7404bad441be54230d6ec60b9 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 23 Aug 2023 12:50:40 +0300 Subject: [PATCH] fix create side wallet --- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 45 +-- cw_bitcoin/lib/electrum_wallet.dart | 6 +- cw_bitcoin/lib/electrum_wallet_addresses.dart | 17 +- .../lib/src/bitcoin_cash_wallet.dart | 381 +++++++++++------- .../src/bitcoin_cash_wallet_addresses.dart | 15 +- 5 files changed, 270 insertions(+), 194 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index de3fdfbca..36d37127d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,39 +1,34 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:cw_bitcoin/electrum.dart'; -import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; part 'bitcoin_wallet_addresses.g.dart'; -class BitcoinWalletAddresses = BitcoinWalletAddressesBase - with _$BitcoinWalletAddresses; +class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses; -abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses - with Store { - BitcoinWalletAddressesBase( - WalletInfo walletInfo, +abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store { + BitcoinWalletAddressesBase(WalletInfo walletInfo, {required bitcoin.HDWallet mainHd, - required bitcoin.HDWallet sideHd, - required bitcoin.NetworkType networkType, - required ElectrumClient electrumClient, - List? initialAddresses, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super( - walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: mainHd, - sideHd: sideHd, - electrumClient: electrumClient, - networkType: networkType); + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType); @override String getAddress({required int index, required bitcoin.HDWallet hd}) => generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); -} \ No newline at end of file +} diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 8aece846c..139ddd324 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -93,7 +93,7 @@ abstract class ElectrumWalletBase static HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => bitcoin.HDWallet.fromSeed(seedBytes, network: bitcoinCashNetworkType) - .derivePath("m/44'/145'/0'/0/0"); + .derivePath("m/44'/145'/0'/0"); static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 146 + outputsCounts * 33 + 8; @@ -154,9 +154,7 @@ abstract class ElectrumWalletBase Future startSync() async { try { syncStatus = AttemptingSyncStatus(); - if (walletInfo.type != WalletType.bitcoinCash) { //TODO: BCH: remove this check when supported - await walletAddresses.discoverAddresses(); - } + await walletAddresses.discoverAddresses(); await updateTransactions(); _subscribeForUpdates(); await updateUnspent(); diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 4aacbef8d..55cc855b4 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -108,8 +108,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { updateChangeAddresses(); if (changeAddresses.isEmpty) { - final newAddresses = await _createNewAddresses( - (walletInfo.type != WalletType.bitcoinCash ? gap : 0), //TODO: BCH: Fix this + final newAddresses = await _createNewAddresses(gap, hd: sideHd, startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 @@ -180,8 +179,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } else { addrs = await _createNewAddresses( isHidden - ? walletInfo.type != WalletType.bitcoinCash ? defaultChangeAddressesCount : 0 //TODO: BCH: Fix this - : walletInfo.type != WalletType.bitcoinCash ? defaultReceiveAddressesCount : 1, //TODO: BCH: Fix this + ? defaultChangeAddressesCount + : defaultReceiveAddressesCount, startIndex: 0, hd: hd, isHidden: isHidden); @@ -196,7 +195,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } final start = addrs.length; - final count = start + (walletInfo.type != WalletType.bitcoinCash ? gap : 0); //TODO: BCH: Fix this + final count = start + gap; final batch = await _createNewAddresses( count, startIndex: start, @@ -223,8 +222,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { countOfReceiveAddresses += 1; }); - if (countOfReceiveAddresses < (walletInfo.type != WalletType.bitcoinCash ? defaultReceiveAddressesCount : 1)) { //TODO: BCH: Fix this - final addressesCount = (walletInfo.type != WalletType.bitcoinCash ? defaultReceiveAddressesCount : 1) - countOfReceiveAddresses; + if (countOfReceiveAddresses < defaultReceiveAddressesCount) { + final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; final newAddresses = await _createNewAddresses( addressesCount, startIndex: countOfReceiveAddresses, @@ -233,8 +232,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { addresses.addAll(newAddresses); } - if (countOfHiddenAddresses < (walletInfo.type != WalletType.bitcoinCash ? defaultChangeAddressesCount : 0)) { //TODO: BCH: Fix this - final addressesCount = (walletInfo.type != WalletType.bitcoinCash ? defaultChangeAddressesCount : 0) - countOfHiddenAddresses; + if (countOfHiddenAddresses < defaultChangeAddressesCount) { + final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; final newAddresses = await _createNewAddresses( addressesCount, startIndex: countOfHiddenAddresses, diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 6f9f07077..6de231637 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -10,6 +10,7 @@ import 'package:cw_bitcoin/bitcoin_unspent.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/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -53,11 +54,22 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, - sideHd: hd, - //TODO: BCH: check if this is correct + sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: bitcoinCashNetworkType) + .derivePath("m/44'/145'/0'/1"), networkType: networkType); } + static bitcoin.NetworkType bitcoinCashNetworkType = bitcoin.NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: bitcoin.Bip32Type( + public: 0x0488b21e, + private: 0x0488ade4, + ), + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80); + static Future create( {required String mnemonic, required String password, @@ -98,158 +110,219 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { initialChangeAddressIndex: snp.changeAddressIndex); } - @override - Future createTransaction(Object credentials) async { - final transactionCredentials = credentials as BitcoinTransactionCredentials; - - const minAmount = 546; - final inputs = []; - var allInputsAmount = 0; - final outputs = transactionCredentials.outputs; - final hasMultiDestination = outputs.length > 1; - - if (unspentCoins.isEmpty) { - await updateUnspent(); - } - - for (final utx in unspentCoins) { - if (utx.isSending) { - allInputsAmount += utx.value; - inputs.add(utx); - } - } - - if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); - - final int feeRate = transactionCredentials.feeRate ?? - BitcoinCashFeeRates.feeRate(transactionCredentials.priority!); - - final int allAmountFee = - bitbox.BitcoinCash.getByteCount(inputs.length, transactionCredentials.outputs.length) * - feeRate; - - final allAmount = allInputsAmount - allAmountFee; - var credentialsAmount = 0; - var amount = 0; - var fee = 0; - - if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - credentialsAmount = outputs.fold(0, (acc, value) { - return acc + value.formattedCryptoAmount!; - }); - - if (allAmount - credentialsAmount < minAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - amount = credentialsAmount; - - if (transactionCredentials.feeRate != null) { - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, - outputsCount: outputs.length + 1); - } else { - fee = calculateEstimatedFee(transactionCredentials.priority, amount, - outputsCount: outputs.length + 1); - } - } else { - final output = outputs.first; - credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; - - if (credentialsAmount > allAmount) throw BitcoinTransactionWrongBalanceException(currency); - - amount = output.sendAll || allAmount - credentialsAmount < minAmount - ? allAmount - : credentialsAmount; - - if (output.sendAll || amount == allAmount) { - fee = allAmountFee; - } else if (transactionCredentials.feeRate != null) { - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); - } else { - fee = calculateEstimatedFee(transactionCredentials.priority, amount); - } - } - - if (fee == 0) throw BitcoinTransactionWrongBalanceException(currency); - - final totalAmount = amount + fee; - - if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - // final changeAddress = await walletAddresses.getChangeAddress(); TODO: BCH: implement change address - var leftAmount = totalAmount; - var totalInputAmount = 0; - - inputs.clear(); - - for (final utx in unspentCoins) { - if (utx.isSending) { - leftAmount = leftAmount - utx.value; - totalInputAmount += utx.value; - inputs.add(utx); - - if (leftAmount <= 0) break; - } - } - - if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); - - if (amount <= 0 || totalInputAmount < totalAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - final builder = bitbox.Bitbox.transactionBuilder(testnet: false); - final _wallet = hd; - - final utxoSigningData = await fetchBuildTxData(inputs, _wallet); - - List _utxos = []; - for (var element in inputs) { - _utxos.add(bitbox.Utxo(element.hash, element.vout, - bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, 1)); - } - - final signatures = []; - int totalBalance = 0; - - _utxos.forEach((bitbox.Utxo utxo) { - builder.addInput(utxo.txid, utxo.vout); - - final ec = utxoSigningData.firstWhere((e) => e.utxo.hash == utxo.txid).keyPair!; - - final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); - - signatures - .add({"vin": signatures.length, "key_pair": bitboxEC, "original_amount": utxo.satoshis}); - - totalBalance += utxo.satoshis; - }); - - outputs.forEach((item) { - final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; - final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; - builder.addOutput(outputAddress, outputAmount!); - }); - - signatures.forEach((signature) { - builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); - }); - - // build the transaction - final tx = builder.build(); - return PendingBitcoinCashTransaction(tx, type, - electrumClient: electrumClient, amount: amount, fee: fee) - ..addListener((transaction) async { - transactionHistory.addOne(transaction); - await updateBalance(); - }); - } + // @override + // Future createTransaction(Object credentials) async { + // final transactionCredentials = credentials as BitcoinTransactionCredentials; + // + // const minAmount = 546; + // final inputs = []; + // var allInputsAmount = 0; + // final outputs = transactionCredentials.outputs; + // final hasMultiDestination = outputs.length > 1; + // + // if (unspentCoins.isEmpty) { + // await updateUnspent(); + // } + // + // for (final utx in unspentCoins) { + // if (utx.isSending) { + // allInputsAmount += utx.value; + // inputs.add(utx); + // } + // } + // + // if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); + // + // final int feeRate = transactionCredentials.feeRate ?? + // BitcoinCashFeeRates.feeRate(transactionCredentials.priority!); + // + // final int allAmountFee = + // bitbox.BitcoinCash.getByteCount(inputs.length, transactionCredentials.outputs.length) * + // feeRate; + // + // final allAmount = allInputsAmount - allAmountFee; + // var credentialsAmount = 0; + // var amount = 0; + // var fee = 0; + // + // if (hasMultiDestination) { + // if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { + // throw BitcoinTransactionWrongBalanceException(currency); + // } + // + // credentialsAmount = outputs.fold(0, (acc, value) { + // return acc + value.formattedCryptoAmount!; + // }); + // + // if (allAmount - credentialsAmount < minAmount) { + // throw BitcoinTransactionWrongBalanceException(currency); + // } + // + // amount = credentialsAmount; + // + // if (transactionCredentials.feeRate != null) { + // fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, + // outputsCount: outputs.length + 1); + // } else { + // fee = calculateEstimatedFee(transactionCredentials.priority, amount, + // outputsCount: outputs.length + 1); + // } + // } else { + // final output = outputs.first; + // credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; + // + // if (credentialsAmount > allAmount) throw BitcoinTransactionWrongBalanceException(currency); + // + // amount = output.sendAll || allAmount - credentialsAmount < minAmount + // ? allAmount + // : credentialsAmount; + // + // if (output.sendAll || amount == allAmount) { + // fee = allAmountFee; + // } else if (transactionCredentials.feeRate != null) { + // fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); + // } else { + // fee = calculateEstimatedFee(transactionCredentials.priority, amount); + // } + // } + // + // if (fee == 0) throw BitcoinTransactionWrongBalanceException(currency); + // + // final totalAmount = amount + fee; + // + // if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { + // throw BitcoinTransactionWrongBalanceException(currency); + // } + // + // // final changeAddress = await walletAddresses.getChangeAddress(); TODO: BCH: implement change address + // var leftAmount = totalAmount; + // var totalInputAmount = 0; + // + // inputs.clear(); + // + // for (final utx in unspentCoins) { + // if (utx.isSending) { + // leftAmount = leftAmount - utx.value; + // totalInputAmount += utx.value; + // inputs.add(utx); + // + // if (leftAmount <= 0) break; + // } + // } + // + // if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); + // + // if (amount <= 0 || totalInputAmount < totalAmount) { + // throw BitcoinTransactionWrongBalanceException(currency); + // } + // + // final builder = bitbox.Bitbox.transactionBuilder(testnet: false); + // final _wallet = hd; + // + // final utxoSigningData = await fetchBuildTxData(inputs, _wallet); + // + // List _utxos = []; + // for (var element in inputs) { + // _utxos.add(bitbox.Utxo(element.hash, element.vout, + // bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, 1)); + // } + // + // final signatures = []; + // int totalBalance = 0; + // + // _utxos.forEach((bitbox.Utxo utxo) { + // builder.addInput(utxo.txid, utxo.vout); + // + // final ec = utxoSigningData.firstWhere((e) => e.utxo.hash == utxo.txid).keyPair!; + // + // final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); + // + // signatures + // .add({"vin": signatures.length, "key_pair": bitboxEC, "original_amount": utxo.satoshis}); + // + // totalBalance += utxo.satoshis; + // }); + // + // outputs.forEach((item) { + // final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; + // final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; + // builder.addOutput(outputAddress, outputAmount!); + // }); + // + // signatures.forEach((signature) { + // builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); + // }); + // + // // build the transaction + // final tx = builder.build(); + // return PendingBitcoinCashTransaction(tx, type, + // electrumClient: electrumClient, amount: amount, fee: fee) + // ..addListener((transaction) async { + // transactionHistory.addOne(transaction); + // await updateBalance(); + // }); + // } + // @override + // Future createTransaction(Object credentials) async { + // final utxosToUse = unspentCoins.where((utxo) => utxo.isSending).toList(); + // final utxoSigningData = await fetchBuildTxData(utxosToUse, hd); + // final builder = bitbox.Bitbox.transactionBuilder(testnet: false); + // + // List _utxos = []; + // for (var element in utxosToUse) { + // _utxos.add(bitbox.Utxo(element.hash, element.vout, + // bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, 1)); + // } + // + // final signatures = []; + // int totalBalance = 0; + // + // _utxos.forEach((bitbox.Utxo utxo) { + // // add the utxo as an input for the transaction + // builder.addInput(utxo.txid, utxo.vout); + // + // final ec = utxoSigningData.firstWhere((e) => e.utxo.hash == utxo.txid).keyPair!; + // + // final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); + // + // // add a signature to the list to be used later + // signatures + // .add({"vin": signatures.length, "key_pair": bitboxEC, "original_amount": utxo.satoshis}); + // + // totalBalance += utxo.satoshis; + // }); + // + // // set an address to send the remaining balance to + // final outputAddress = "13Hvge9HRduGiXMfcJHFn6sggequmaKqsZ"; + // + // // if there is an unspent balance, create a spending transaction + // if (totalBalance > 0 && outputAddress != "") { + // // calculate the fee based on number of inputs and one expected output + // final fee = bitbox.BitcoinCash.getByteCount(signatures.length, 1); + // + // // calculate how much balance will be left over to spend after the fee + // final sendAmount = totalBalance - fee; + // + // // add the output based on the address provided in the testing data + // builder.addOutput(outputAddress, sendAmount); + // + // // sign all inputs + // signatures.forEach((signature) { + // builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); + // }); + // + // // build the transaction + // final tx = builder.build(); + // + // // broadcast the transaction + // final result = await electrumClient.broadcastTransaction(transactionRaw: tx.toHex()); + // + // // Yatta! + // print("Transaction broadcasted: $result"); + // } + // return PendingBitcoinTransaction(Transaction(), type, + // electrumClient: electrumClient, amount: 1, fee: 1); + // } Future> fetchBuildTxData( List utxosToUse, HDWallet wallet) async { diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart index c7329f0de..600f2b307 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -1,8 +1,8 @@ -import 'package:bitbox/bitbox.dart' as Bitbox; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; @@ -30,5 +30,16 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi @override String getAddress({required int index, required bitcoin.HDWallet hd}) => - hd.address!; + generateP2PKHAddress(hd: hd, index: index, networkType: bitcoinCashNetworkType); + + static bitcoin.NetworkType bitcoinCashNetworkType = bitcoin.NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: bitcoin.Bip32Type( + public: 0x0488b21e, + private: 0x0488ade4, + ), + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80); }