fix: _address vs address, missing p2sh

This commit is contained in:
Rafael Saes 2024-02-09 11:19:55 -03:00
parent 2e6ba71e57
commit f8cadfa112
9 changed files with 87 additions and 47 deletions

View file

@ -11,6 +11,8 @@ String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
return P2shAddress.fromScriptPubkey(script: script).toAddress(network); return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
case SegwitAddresType.p2wpkh: case SegwitAddresType.p2wpkh:
return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network); return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network);
case P2shAddressType.p2pkhInP2sh:
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
case SegwitAddresType.p2wsh: case SegwitAddresType.p2wsh:
return P2wshAddress.fromScriptPubkey(script: script).toAddress(network); return P2wshAddress.fromScriptPubkey(script: script).toAddress(network);
case SegwitAddresType.p2tr: case SegwitAddresType.p2tr:

View file

@ -12,7 +12,7 @@ class BitcoinAddressRecord {
int balance = 0, int balance = 0,
String name = '', String name = '',
bool isUsed = false, bool isUsed = false,
this.type, required this.type,
}) : _txCount = txCount, }) : _txCount = txCount,
_balance = balance, _balance = balance,
_name = name, _name = name,
@ -32,7 +32,7 @@ class BitcoinAddressRecord {
type: decoded['type'] != null && decoded['type'] != '' type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values ? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String) .firstWhere((type) => type.toString() == decoded['type'] as String)
: null, : SegwitAddresType.p2wpkh,
); );
} }
@ -67,7 +67,7 @@ class BitcoinAddressRecord {
String get cashAddr => bitbox.Address.toCashAddress(address); String get cashAddr => bitbox.Address.toCashAddress(address);
BitcoinAddressType? type; BitcoinAddressType type;
String toJSON() => json.encode({ String toJSON() => json.encode({
'address': address, 'address': address,
@ -77,6 +77,6 @@ class BitcoinAddressRecord {
'txCount': txCount, 'txCount': txCount,
'name': name, 'name': name,
'balance': balance, 'balance': balance,
'type': type?.toString() ?? '', 'type': type.toString(),
}); });
} }

View file

@ -3,6 +3,7 @@ import 'package:cw_core/receive_page_option.dart';
class BitcoinReceivePageOption implements ReceivePageOption { class BitcoinReceivePageOption implements ReceivePageOption {
static const p2wpkh = BitcoinReceivePageOption._('Segwit (P2WPKH)'); static const p2wpkh = BitcoinReceivePageOption._('Segwit (P2WPKH)');
static const p2sh = BitcoinReceivePageOption._('Segwit-Compatible (P2SH)');
static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)'); static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)');
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)'); static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)'); static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
@ -17,6 +18,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
static const all = [ static const all = [
BitcoinReceivePageOption.p2wpkh, BitcoinReceivePageOption.p2wpkh,
BitcoinReceivePageOption.p2sh,
BitcoinReceivePageOption.p2tr, BitcoinReceivePageOption.p2tr,
BitcoinReceivePageOption.p2wsh, BitcoinReceivePageOption.p2wsh,
BitcoinReceivePageOption.p2pkh BitcoinReceivePageOption.p2pkh
@ -30,6 +32,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
return BitcoinReceivePageOption.p2wsh; return BitcoinReceivePageOption.p2wsh;
case P2pkhAddressType.p2pkh: case P2pkhAddressType.p2pkh:
return BitcoinReceivePageOption.p2pkh; return BitcoinReceivePageOption.p2pkh;
case P2shAddressType.p2wpkhInP2sh:
return BitcoinReceivePageOption.p2pkh;
case SegwitAddresType.p2wpkh: case SegwitAddresType.p2wpkh:
default: default:
return BitcoinReceivePageOption.p2wpkh; return BitcoinReceivePageOption.p2wpkh;

View file

@ -32,6 +32,9 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
if (addressType == SegwitAddresType.p2wsh) if (addressType == SegwitAddresType.p2wsh)
return generateP2WSHAddress(hd: hd, index: index, network: network); return generateP2WSHAddress(hd: hd, index: index, network: network);
if (addressType == P2shAddressType.p2wpkhInP2sh)
return generateP2SHAddress(hd: hd, index: index, network: network);
return generateP2WPKHAddress(hd: hd, index: index, network: network); return generateP2WPKHAddress(hd: hd, index: index, network: network);
} }
} }

View file

@ -157,7 +157,7 @@ abstract class ElectrumWalletBase
Future<void> startSync() async { Future<void> startSync() async {
try { try {
syncStatus = AttemptingSyncStatus(); syncStatus = AttemptingSyncStatus();
await walletAddresses.discoverAddresses(); await walletAddresses.discoverAddressesAll();
await updateTransactions(); await updateTransactions();
_subscribeForUpdates(); _subscribeForUpdates();
await updateUnspent(); await updateUnspent();
@ -849,6 +849,8 @@ class EstimatedTxResult {
BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) { BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) {
if (P2pkhAddress.regex.hasMatch(address)) { if (P2pkhAddress.regex.hasMatch(address)) {
return P2pkhAddress.fromAddress(address: address, network: network); return P2pkhAddress.fromAddress(address: address, network: network);
} else if (P2shAddress.regex.hasMatch(address)) {
return P2shAddress.fromAddress(address: address, network: network);
} else if (P2wshAddress.regex.hasMatch(address)) { } else if (P2wshAddress.regex.hasMatch(address)) {
return P2wshAddress.fromAddress(address: address, network: network); return P2wshAddress.fromAddress(address: address, network: network);
} else if (P2trAddress.regex.hasMatch(address)) { } else if (P2trAddress.regex.hasMatch(address)) {
@ -861,6 +863,8 @@ BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network)
BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
if (type is P2pkhAddress) { if (type is P2pkhAddress) {
return P2pkhAddressType.p2pkh; return P2pkhAddressType.p2pkh;
} else if (type is P2shAddress) {
return P2shAddressType.p2wpkhInP2sh;
} else if (type is P2wshAddress) { } else if (type is P2wshAddress) {
return SegwitAddresType.p2wsh; return SegwitAddresType.p2wsh;
} else if (type is P2trAddress) { } else if (type is P2trAddress) {

View file

@ -65,7 +65,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
String get addressPageTypeStr => addressPageType.toString(); String get addressPageTypeStr => addressPageType.toString();
@computed @computed
List<BitcoinAddressRecord> get addresses => _addresses.where(_isAddressTypeMatch).toList(); List<BitcoinAddressRecord> get addresses => _addresses.where(_isAddressPageTypeMatch).toList();
@computed @computed
List<BitcoinAddressRecord> get allAddresses => _addresses; List<BitcoinAddressRecord> get allAddresses => _addresses;
@ -80,7 +80,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
String get address { String get address {
String receiveAddress; String receiveAddress;
final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressTypeMatch); final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch);
if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) ||
typeMatchingReceiveAddresses.isEmpty) { typeMatchingReceiveAddresses.isEmpty) {
@ -151,15 +151,28 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return acc; return acc;
}); });
Future<void> discoverAddresses() async { Future<void> discoverAddressesAll() async {
await _discoverAddresses(false); await _discoverAddresses(false);
await _discoverAddresses(true); await _discoverAddresses(true);
await _discoverAddresses(false, type: P2pkhAddressType.p2pkh);
await _discoverAddresses(true, type: P2pkhAddressType.p2pkh);
await _discoverAddresses(false, type: P2shAddressType.p2wpkhInP2sh);
await _discoverAddresses(true, type: P2shAddressType.p2wpkhInP2sh);
await _discoverAddresses(false, type: SegwitAddresType.p2tr);
await _discoverAddresses(true, type: SegwitAddresType.p2tr);
await _discoverAddresses(false, type: SegwitAddresType.p2wsh);
await _discoverAddresses(true, type: SegwitAddresType.p2wsh);
updateReceiveAddresses();
await updateAddressesInBox(); await updateAddressesInBox();
} }
@override @override
Future<void> init() async { Future<void> init() async {
await _generateInitialAddresses(); await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
updateReceiveAddresses(); updateReceiveAddresses();
updateChangeAddresses(); updateChangeAddresses();
await updateAddressesInBox(); await updateAddressesInBox();
@ -199,11 +212,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc); 0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
final address = BitcoinAddressRecord( final address = BitcoinAddressRecord(
getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType), getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType),
index: newAddressIndex, index: newAddressIndex,
isHidden: false, isHidden: false,
name: label, name: label,
); type: addressPageType);
addresses.add(address); addresses.add(address);
return address; return address;
} }
@ -240,31 +253,36 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void updateReceiveAddresses() { void updateReceiveAddresses() {
receiveAddresses.removeRange(0, receiveAddresses.length); receiveAddresses.removeRange(0, receiveAddresses.length);
final newAddresses = final newAddresses =
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); _addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
receiveAddresses.addAll(newAddresses); receiveAddresses.addAll(newAddresses);
} }
@action @action
void updateChangeAddresses() { void updateChangeAddresses() {
changeAddresses.removeRange(0, changeAddresses.length); changeAddresses.removeRange(0, changeAddresses.length);
final newAddresses = final newAddresses = _addresses.where((addressRecord) =>
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed); addressRecord.isHidden &&
!addressRecord.isUsed &&
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
addressRecord.type == SegwitAddresType.p2wpkh);
changeAddresses.addAll(newAddresses); changeAddresses.addAll(newAddresses);
} }
@action @action
Future<void> _discoverAddresses(bool isHidden) async { Future<void> _discoverAddresses(bool isHidden,
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
var hasAddrUse = true; var hasAddrUse = true;
List<BitcoinAddressRecord> addrs; List<BitcoinAddressRecord> addrs;
final matchingAddresses = _addresses.where((addr) => _addressMatchHidden(addr, isHidden, type));
if (addresses.where((addr) => addr.isHidden == isHidden).isNotEmpty) { if (matchingAddresses.isNotEmpty) {
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList(); addrs = matchingAddresses.toList();
} else { } else {
addrs = await _createNewAddresses( addrs = await _createNewAddresses(
isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount, isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
startIndex: 0, startIndex: 0,
isHidden: isHidden, isHidden: isHidden,
); type: type);
} }
while (hasAddrUse) { while (hasAddrUse) {
@ -277,51 +295,55 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final start = addrs.length; final start = addrs.length;
final count = start + gap; final count = start + gap;
final batch = await _createNewAddresses(count, startIndex: start, isHidden: isHidden); final batch =
await _createNewAddresses(count, startIndex: start, isHidden: isHidden, type: type);
addrs.addAll(batch); addrs.addAll(batch);
} }
addAddresses(addrs); addAddresses(addrs);
} }
Future<void> _generateInitialAddresses() async { Future<void> _generateInitialAddresses(
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
var countOfReceiveAddresses = 0; var countOfReceiveAddresses = 0;
var countOfHiddenAddresses = 0; var countOfHiddenAddresses = 0;
addresses.forEach((addr) { _addresses.forEach((addr) {
if (addr.isHidden) { if (_isAddressByType(addr, type)) {
countOfHiddenAddresses += 1; if (addr.isHidden) {
return; countOfHiddenAddresses += 1;
} return;
}
countOfReceiveAddresses += 1; countOfReceiveAddresses += 1;
}
}); });
if (countOfReceiveAddresses < defaultReceiveAddressesCount) { if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
final newAddresses = await _createNewAddresses(addressesCount, final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfReceiveAddresses, isHidden: false); startIndex: countOfReceiveAddresses, isHidden: false, type: type);
addresses.addAll(newAddresses); _addresses.addAll(newAddresses);
} }
if (countOfHiddenAddresses < defaultChangeAddressesCount) { if (countOfHiddenAddresses < defaultChangeAddressesCount) {
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
final newAddresses = await _createNewAddresses(addressesCount, final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfHiddenAddresses, isHidden: true); startIndex: countOfHiddenAddresses, isHidden: true, type: type);
addresses.addAll(newAddresses); _addresses.addAll(newAddresses);
} }
} }
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count, Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
{int startIndex = 0, bool isHidden = false}) async { {int startIndex = 0, bool isHidden = false, BitcoinAddressType? type}) async {
final list = <BitcoinAddressRecord>[]; final list = <BitcoinAddressRecord>[];
for (var i = startIndex; i < count + startIndex; i++) { for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord( final address = BitcoinAddressRecord(
getAddress(index: i, hd: _getHd(isHidden), addressType: addressPageType), getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
index: i, index: i,
isHidden: isHidden, isHidden: isHidden,
type: addressPageType, type: type ?? addressPageType,
); );
list.add(address); list.add(address);
} }
@ -346,17 +368,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
Future<void> setAddressType(BitcoinAddressType type) async { Future<void> setAddressType(BitcoinAddressType type) async {
_addressPageType = type; _addressPageType = type;
await discoverAddresses();
await saveAddressesInBox();
} }
bool _isAddressTypeMatch(BitcoinAddressRecord addressRecord) { bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) {
// Old wallets before address types were introduced will have an empty address record type return _isAddressByType(addressRecord, addressPageType);
return addressPageType == SegwitAddresType.p2wpkh
? addressRecord.type == null || addressRecord.type == addressPageType
: addressRecord.type == addressPageType;
} }
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd; bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
addr.type == type || (addr.type == null && type == SegwitAddresType.p2wpkh);
bool _addressMatchHidden(BitcoinAddressRecord addr, bool isHidden, BitcoinAddressType type) =>
_isAddressByType(addr, type) && addr.isHidden == isHidden;
} }

View file

@ -16,6 +16,10 @@ String generateP2WPKHAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network); ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network);
String generateP2SHAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network);
String generateP2WSHAddress( String generateP2WSHAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) => {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network); ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network);

View file

@ -80,7 +80,7 @@ packages:
description: description:
path: "." path: "."
ref: cake-update-v1 ref: cake-update-v1
resolved-ref: "318986da9cb03e4c18da2e6c0daf3e62aef7eb72" resolved-ref: "9611e9db77e92a8434e918cdfb620068f6fcb1aa"
url: "https://github.com/cake-tech/bitcoin_base.git" url: "https://github.com/cake-tech/bitcoin_base.git"
source: git source: git
version: "4.0.0" version: "4.0.0"

View file

@ -233,6 +233,9 @@ class AddressPage extends BasePage {
case BitcoinReceivePageOption.p2pkh: case BitcoinReceivePageOption.p2pkh:
addressListViewModel.setAddressType(P2pkhAddressType.p2pkh); addressListViewModel.setAddressType(P2pkhAddressType.p2pkh);
break; break;
case BitcoinReceivePageOption.p2sh:
addressListViewModel.setAddressType(P2shAddressType.p2wpkhInP2sh);
break;
case BitcoinReceivePageOption.p2wpkh: case BitcoinReceivePageOption.p2wpkh:
addressListViewModel.setAddressType(SegwitAddresType.p2wpkh); addressListViewModel.setAddressType(SegwitAddresType.p2wpkh);
break; break;