fix create side wallet

This commit is contained in:
Serhii 2023-08-23 12:50:40 +03:00
parent d150f06b39
commit 628fb212fb
5 changed files with 270 additions and 194 deletions

View file

@ -1,37 +1,32 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; 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/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'bitcoin_wallet_addresses.g.dart'; part 'bitcoin_wallet_addresses.g.dart';
class BitcoinWalletAddresses = BitcoinWalletAddressesBase class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
with _$BitcoinWalletAddresses;
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
with Store { BitcoinWalletAddressesBase(WalletInfo walletInfo,
BitcoinWalletAddressesBase(
WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd, {required bitcoin.HDWallet mainHd,
required bitcoin.HDWallet sideHd, required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType, required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient, required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0}) int initialChangeAddressIndex = 0})
: super( : super(walletInfo,
walletInfo, initialAddresses: initialAddresses,
initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex,
initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, mainHd: mainHd,
mainHd: mainHd, sideHd: sideHd,
sideHd: sideHd, electrumClient: electrumClient,
electrumClient: electrumClient, networkType: networkType);
networkType: networkType);
@override @override
String getAddress({required int index, required bitcoin.HDWallet hd}) => String getAddress({required int index, required bitcoin.HDWallet hd}) =>

View file

@ -93,7 +93,7 @@ abstract class ElectrumWalletBase
static HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => static HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
bitcoin.HDWallet.fromSeed(seedBytes, network: bitcoinCashNetworkType) 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) => static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 146 + outputsCounts * 33 + 8; inputsCount * 146 + outputsCounts * 33 + 8;
@ -154,9 +154,7 @@ abstract class ElectrumWalletBase
Future<void> startSync() async { Future<void> startSync() async {
try { try {
syncStatus = AttemptingSyncStatus(); syncStatus = AttemptingSyncStatus();
if (walletInfo.type != WalletType.bitcoinCash) { //TODO: BCH: remove this check when supported await walletAddresses.discoverAddresses();
await walletAddresses.discoverAddresses();
}
await updateTransactions(); await updateTransactions();
_subscribeForUpdates(); _subscribeForUpdates();
await updateUnspent(); await updateUnspent();

View file

@ -108,8 +108,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
updateChangeAddresses(); updateChangeAddresses();
if (changeAddresses.isEmpty) { if (changeAddresses.isEmpty) {
final newAddresses = await _createNewAddresses( final newAddresses = await _createNewAddresses(gap,
(walletInfo.type != WalletType.bitcoinCash ? gap : 0), //TODO: BCH: Fix this
hd: sideHd, hd: sideHd,
startIndex: totalCountOfChangeAddresses > 0 startIndex: totalCountOfChangeAddresses > 0
? totalCountOfChangeAddresses - 1 ? totalCountOfChangeAddresses - 1
@ -180,8 +179,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} else { } else {
addrs = await _createNewAddresses( addrs = await _createNewAddresses(
isHidden isHidden
? walletInfo.type != WalletType.bitcoinCash ? defaultChangeAddressesCount : 0 //TODO: BCH: Fix this ? defaultChangeAddressesCount
: walletInfo.type != WalletType.bitcoinCash ? defaultReceiveAddressesCount : 1, //TODO: BCH: Fix this : defaultReceiveAddressesCount,
startIndex: 0, startIndex: 0,
hd: hd, hd: hd,
isHidden: isHidden); isHidden: isHidden);
@ -196,7 +195,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
final start = addrs.length; 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( final batch = await _createNewAddresses(
count, count,
startIndex: start, startIndex: start,
@ -223,8 +222,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
countOfReceiveAddresses += 1; countOfReceiveAddresses += 1;
}); });
if (countOfReceiveAddresses < (walletInfo.type != WalletType.bitcoinCash ? defaultReceiveAddressesCount : 1)) { //TODO: BCH: Fix this if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
final addressesCount = (walletInfo.type != WalletType.bitcoinCash ? defaultReceiveAddressesCount : 1) - countOfReceiveAddresses; final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
final newAddresses = await _createNewAddresses( final newAddresses = await _createNewAddresses(
addressesCount, addressesCount,
startIndex: countOfReceiveAddresses, startIndex: countOfReceiveAddresses,
@ -233,8 +232,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addresses.addAll(newAddresses); addresses.addAll(newAddresses);
} }
if (countOfHiddenAddresses < (walletInfo.type != WalletType.bitcoinCash ? defaultChangeAddressesCount : 0)) { //TODO: BCH: Fix this if (countOfHiddenAddresses < defaultChangeAddressesCount) {
final addressesCount = (walletInfo.type != WalletType.bitcoinCash ? defaultChangeAddressesCount : 0) - countOfHiddenAddresses; final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
final newAddresses = await _createNewAddresses( final newAddresses = await _createNewAddresses(
addressesCount, addressesCount,
startIndex: countOfHiddenAddresses, startIndex: countOfHiddenAddresses,

View file

@ -10,6 +10,7 @@ import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.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_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
@ -53,11 +54,22 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd, mainHd: hd,
sideHd: hd, sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: bitcoinCashNetworkType)
//TODO: BCH: check if this is correct .derivePath("m/44'/145'/0'/1"),
networkType: networkType); 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<BitcoinCashWallet> create( static Future<BitcoinCashWallet> create(
{required String mnemonic, {required String mnemonic,
required String password, required String password,
@ -98,158 +110,219 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
initialChangeAddressIndex: snp.changeAddressIndex); initialChangeAddressIndex: snp.changeAddressIndex);
} }
@override // @override
Future<PendingTransaction> createTransaction(Object credentials) async { // Future<PendingTransaction> createTransaction(Object credentials) async {
final transactionCredentials = credentials as BitcoinTransactionCredentials; // final transactionCredentials = credentials as BitcoinTransactionCredentials;
//
const minAmount = 546; // const minAmount = 546;
final inputs = <BitcoinUnspent>[]; // final inputs = <BitcoinUnspent>[];
var allInputsAmount = 0; // var allInputsAmount = 0;
final outputs = transactionCredentials.outputs; // final outputs = transactionCredentials.outputs;
final hasMultiDestination = outputs.length > 1; // final hasMultiDestination = outputs.length > 1;
//
if (unspentCoins.isEmpty) { // if (unspentCoins.isEmpty) {
await updateUnspent(); // await updateUnspent();
} // }
//
for (final utx in unspentCoins) { // for (final utx in unspentCoins) {
if (utx.isSending) { // if (utx.isSending) {
allInputsAmount += utx.value; // allInputsAmount += utx.value;
inputs.add(utx); // inputs.add(utx);
} // }
} // }
//
if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); // if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
//
final int feeRate = transactionCredentials.feeRate ?? // final int feeRate = transactionCredentials.feeRate ??
BitcoinCashFeeRates.feeRate(transactionCredentials.priority!); // BitcoinCashFeeRates.feeRate(transactionCredentials.priority!);
//
final int allAmountFee = // final int allAmountFee =
bitbox.BitcoinCash.getByteCount(inputs.length, transactionCredentials.outputs.length) * // bitbox.BitcoinCash.getByteCount(inputs.length, transactionCredentials.outputs.length) *
feeRate; // feeRate;
//
final allAmount = allInputsAmount - allAmountFee; // final allAmount = allInputsAmount - allAmountFee;
var credentialsAmount = 0; // var credentialsAmount = 0;
var amount = 0; // var amount = 0;
var fee = 0; // var fee = 0;
//
if (hasMultiDestination) { // if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { // if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
throw BitcoinTransactionWrongBalanceException(currency); // throw BitcoinTransactionWrongBalanceException(currency);
} // }
//
credentialsAmount = outputs.fold(0, (acc, value) { // credentialsAmount = outputs.fold(0, (acc, value) {
return acc + value.formattedCryptoAmount!; // return acc + value.formattedCryptoAmount!;
}); // });
//
if (allAmount - credentialsAmount < minAmount) { // if (allAmount - credentialsAmount < minAmount) {
throw BitcoinTransactionWrongBalanceException(currency); // throw BitcoinTransactionWrongBalanceException(currency);
} // }
//
amount = credentialsAmount; // amount = credentialsAmount;
//
if (transactionCredentials.feeRate != null) { // if (transactionCredentials.feeRate != null) {
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, // fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
outputsCount: outputs.length + 1); // outputsCount: outputs.length + 1);
} else { // } else {
fee = calculateEstimatedFee(transactionCredentials.priority, amount, // fee = calculateEstimatedFee(transactionCredentials.priority, amount,
outputsCount: outputs.length + 1); // outputsCount: outputs.length + 1);
} // }
} else { // } else {
final output = outputs.first; // final output = outputs.first;
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; // credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
//
if (credentialsAmount > allAmount) throw BitcoinTransactionWrongBalanceException(currency); // if (credentialsAmount > allAmount) throw BitcoinTransactionWrongBalanceException(currency);
//
amount = output.sendAll || allAmount - credentialsAmount < minAmount // amount = output.sendAll || allAmount - credentialsAmount < minAmount
? allAmount // ? allAmount
: credentialsAmount; // : credentialsAmount;
//
if (output.sendAll || amount == allAmount) { // if (output.sendAll || amount == allAmount) {
fee = allAmountFee; // fee = allAmountFee;
} else if (transactionCredentials.feeRate != null) { // } else if (transactionCredentials.feeRate != null) {
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); // fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
} else { // } else {
fee = calculateEstimatedFee(transactionCredentials.priority, amount); // fee = calculateEstimatedFee(transactionCredentials.priority, amount);
} // }
} // }
//
if (fee == 0) throw BitcoinTransactionWrongBalanceException(currency); // if (fee == 0) throw BitcoinTransactionWrongBalanceException(currency);
//
final totalAmount = amount + fee; // final totalAmount = amount + fee;
//
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { // if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
throw BitcoinTransactionWrongBalanceException(currency); // throw BitcoinTransactionWrongBalanceException(currency);
} // }
//
// final changeAddress = await walletAddresses.getChangeAddress(); TODO: BCH: implement change address // // final changeAddress = await walletAddresses.getChangeAddress(); TODO: BCH: implement change address
var leftAmount = totalAmount; // var leftAmount = totalAmount;
var totalInputAmount = 0; // var totalInputAmount = 0;
//
inputs.clear(); // inputs.clear();
//
for (final utx in unspentCoins) { // for (final utx in unspentCoins) {
if (utx.isSending) { // if (utx.isSending) {
leftAmount = leftAmount - utx.value; // leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value; // totalInputAmount += utx.value;
inputs.add(utx); // inputs.add(utx);
//
if (leftAmount <= 0) break; // if (leftAmount <= 0) break;
} // }
} // }
//
if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); // if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
//
if (amount <= 0 || totalInputAmount < totalAmount) { // if (amount <= 0 || totalInputAmount < totalAmount) {
throw BitcoinTransactionWrongBalanceException(currency); // throw BitcoinTransactionWrongBalanceException(currency);
} // }
//
final builder = bitbox.Bitbox.transactionBuilder(testnet: false); // final builder = bitbox.Bitbox.transactionBuilder(testnet: false);
final _wallet = hd; // final _wallet = hd;
//
final utxoSigningData = await fetchBuildTxData(inputs, _wallet); // final utxoSigningData = await fetchBuildTxData(inputs, _wallet);
//
List<bitbox.Utxo> _utxos = []; // List<bitbox.Utxo> _utxos = [];
for (var element in inputs) { // for (var element in inputs) {
_utxos.add(bitbox.Utxo(element.hash, element.vout, // _utxos.add(bitbox.Utxo(element.hash, element.vout,
bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, 1)); // bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, 1));
} // }
//
final signatures = <Map>[]; // final signatures = <Map>[];
int totalBalance = 0; // int totalBalance = 0;
//
_utxos.forEach((bitbox.Utxo utxo) { // _utxos.forEach((bitbox.Utxo utxo) {
builder.addInput(utxo.txid, utxo.vout); // builder.addInput(utxo.txid, utxo.vout);
//
final ec = utxoSigningData.firstWhere((e) => e.utxo.hash == utxo.txid).keyPair!; // final ec = utxoSigningData.firstWhere((e) => e.utxo.hash == utxo.txid).keyPair!;
//
final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); // final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF());
//
signatures // signatures
.add({"vin": signatures.length, "key_pair": bitboxEC, "original_amount": utxo.satoshis}); // .add({"vin": signatures.length, "key_pair": bitboxEC, "original_amount": utxo.satoshis});
//
totalBalance += utxo.satoshis; // totalBalance += utxo.satoshis;
}); // });
//
outputs.forEach((item) { // outputs.forEach((item) {
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; // final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; // final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
builder.addOutput(outputAddress, outputAmount!); // builder.addOutput(outputAddress, outputAmount!);
}); // });
//
signatures.forEach((signature) { // signatures.forEach((signature) {
builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); // builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]);
}); // });
//
// build the transaction // // build the transaction
final tx = builder.build(); // final tx = builder.build();
return PendingBitcoinCashTransaction(tx, type, // return PendingBitcoinCashTransaction(tx, type,
electrumClient: electrumClient, amount: amount, fee: fee) // electrumClient: electrumClient, amount: amount, fee: fee)
..addListener((transaction) async { // ..addListener((transaction) async {
transactionHistory.addOne(transaction); // transactionHistory.addOne(transaction);
await updateBalance(); // await updateBalance();
}); // });
} // }
// @override
// Future<PendingBitcoinTransaction> 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<bitbox.Utxo> _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 = <Map>[];
// 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<List<SigningData>> fetchBuildTxData( Future<List<SigningData>> fetchBuildTxData(
List<BitcoinUnspent> utxosToUse, HDWallet wallet) async { List<BitcoinUnspent> utxosToUse, HDWallet wallet) async {

View file

@ -1,8 +1,8 @@
import 'package:bitbox/bitbox.dart' as Bitbox;
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -30,5 +30,16 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
@override @override
String getAddress({required int index, required bitcoin.HDWallet hd}) => 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);
} }