2021-12-24 12:52:08 +00:00
|
|
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
|
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
2022-01-18 16:27:33 +00:00
|
|
|
import 'package:cw_bitcoin/electrum.dart';
|
|
|
|
import 'package:cw_bitcoin/script_hash.dart';
|
2021-12-24 12:52:08 +00:00
|
|
|
import 'package:cw_core/wallet_addresses.dart';
|
|
|
|
import 'package:cw_core/wallet_info.dart';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
|
|
|
|
part 'electrum_wallet_addresses.g.dart';
|
|
|
|
|
|
|
|
class ElectrumWalletAddresses = ElectrumWalletAddressesBase
|
|
|
|
with _$ElectrumWalletAddresses;
|
|
|
|
|
|
|
|
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|
|
|
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
|
|
|
{@required List<BitcoinAddressRecord> initialAddresses,
|
2022-01-18 16:27:33 +00:00
|
|
|
int initialRegularAddressIndex = 0,
|
|
|
|
int initialChangeAddressIndex = 0,
|
2021-12-24 12:52:08 +00:00
|
|
|
this.mainHd,
|
2022-01-18 16:27:33 +00:00
|
|
|
this.sideHd,
|
|
|
|
this.electrumClient,
|
|
|
|
this.networkType})
|
2022-01-24 12:04:23 +00:00
|
|
|
: addresses = ObservableList<BitcoinAddressRecord>.of(
|
|
|
|
(initialAddresses ?? []).toSet()),
|
|
|
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
|
|
|
|
(initialAddresses ?? [])
|
|
|
|
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
|
|
|
.toSet()),
|
|
|
|
changeAddresses = ObservableList<BitcoinAddressRecord>.of(
|
|
|
|
(initialAddresses ?? [])
|
|
|
|
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
|
|
|
.toSet()),
|
|
|
|
super(walletInfo) {
|
2022-01-18 16:27:33 +00:00
|
|
|
currentReceiveAddressIndex = initialRegularAddressIndex;
|
|
|
|
currentChangeAddressIndex = initialChangeAddressIndex;
|
2021-12-24 12:52:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 16:27:33 +00:00
|
|
|
static const defaultReceiveAddressesCount = 22;
|
|
|
|
static const defaultChangeAddressesCount = 17;
|
|
|
|
static const gap = 20;
|
2021-12-24 12:52:08 +00:00
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
final ObservableList<BitcoinAddressRecord> addresses;
|
|
|
|
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
|
|
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
|
|
|
final ElectrumClient electrumClient;
|
|
|
|
final bitcoin.NetworkType networkType;
|
|
|
|
final bitcoin.HDWallet mainHd;
|
|
|
|
final bitcoin.HDWallet sideHd;
|
|
|
|
|
|
|
|
@override
|
|
|
|
@computed
|
|
|
|
String get address => receiveAddresses.first.address;
|
|
|
|
|
2021-12-24 12:52:08 +00:00
|
|
|
@override
|
2022-01-24 12:04:23 +00:00
|
|
|
set address(String addr) => null;
|
2021-12-24 12:52:08 +00:00
|
|
|
|
2022-01-18 16:27:33 +00:00
|
|
|
int currentReceiveAddressIndex;
|
|
|
|
int currentChangeAddressIndex;
|
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
@computed
|
|
|
|
int get totalCountOfReceiveAddresses =>
|
|
|
|
addresses.fold(0, (acc, addressRecord) {
|
|
|
|
if (!addressRecord.isHidden) {
|
|
|
|
return acc + 1;
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
});
|
2022-01-18 17:10:37 +00:00
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
@computed
|
|
|
|
int get totalCountOfChangeAddresses =>
|
|
|
|
addresses.fold(0, (acc, addressRecord) {
|
|
|
|
if (addressRecord.isHidden) {
|
|
|
|
return acc + 1;
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
});
|
2022-01-18 17:10:37 +00:00
|
|
|
|
2022-01-18 16:27:33 +00:00
|
|
|
Future<void> discoverAddresses() async {
|
|
|
|
await _discoverAddresses(mainHd, false);
|
|
|
|
await _discoverAddresses(sideHd, true);
|
|
|
|
await updateAddressesInBox();
|
|
|
|
}
|
2021-12-24 12:52:08 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> init() async {
|
2022-01-18 16:27:33 +00:00
|
|
|
await _generateInitialAddresses();
|
2022-01-24 12:04:23 +00:00
|
|
|
updateReceiveAddresses();
|
|
|
|
updateChangeAddresses();
|
2021-12-24 12:52:08 +00:00
|
|
|
await updateAddressesInBox();
|
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
if (currentReceiveAddressIndex >= receiveAddresses.length) {
|
2022-01-18 16:27:33 +00:00
|
|
|
currentReceiveAddressIndex = 0;
|
2021-12-24 12:52:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
|
|
|
currentChangeAddressIndex = 0;
|
|
|
|
}
|
2021-12-24 12:52:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 16:27:33 +00:00
|
|
|
@action
|
|
|
|
Future<String> getChangeAddress() async {
|
2022-01-24 12:04:23 +00:00
|
|
|
updateChangeAddresses();
|
|
|
|
|
2022-01-18 16:27:33 +00:00
|
|
|
if (changeAddresses.isEmpty) {
|
|
|
|
final newAddresses = await _createNewAddresses(
|
2022-01-18 17:10:37 +00:00
|
|
|
gap,
|
|
|
|
hd: sideHd,
|
2022-01-24 12:04:23 +00:00
|
|
|
startIndex: totalCountOfChangeAddresses > 0
|
|
|
|
? totalCountOfChangeAddresses - 1
|
2022-01-18 17:10:37 +00:00
|
|
|
: 0,
|
2022-01-18 16:27:33 +00:00
|
|
|
isHidden: true);
|
|
|
|
_addAddresses(newAddresses);
|
2022-01-24 12:04:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
2022-01-18 16:27:33 +00:00
|
|
|
currentChangeAddressIndex = 0;
|
2021-12-24 12:52:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
updateChangeAddresses();
|
2022-01-18 16:27:33 +00:00
|
|
|
final address = changeAddresses[currentChangeAddressIndex].address;
|
|
|
|
currentChangeAddressIndex += 1;
|
|
|
|
return address;
|
2021-12-24 12:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<BitcoinAddressRecord> generateNewAddress(
|
|
|
|
{bool isHidden = false, bitcoin.HDWallet hd}) async {
|
2022-01-18 16:27:33 +00:00
|
|
|
currentReceiveAddressIndex += 1;
|
2021-12-24 12:52:08 +00:00
|
|
|
final address = BitcoinAddressRecord(
|
2022-01-18 16:27:33 +00:00
|
|
|
getAddress(index: currentReceiveAddressIndex, hd: hd),
|
|
|
|
index: currentReceiveAddressIndex,
|
2021-12-24 12:52:08 +00:00
|
|
|
isHidden: isHidden);
|
|
|
|
addresses.add(address);
|
|
|
|
return address;
|
|
|
|
}
|
|
|
|
|
|
|
|
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> updateAddressesInBox() async {
|
|
|
|
try {
|
|
|
|
addressesMap.clear();
|
|
|
|
addressesMap[address] = '';
|
|
|
|
await saveAddressesInBox();
|
|
|
|
} catch (e) {
|
|
|
|
print(e.toString());
|
|
|
|
}
|
|
|
|
}
|
2022-01-12 13:20:43 +00:00
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
@action
|
|
|
|
void updateReceiveAddresses() {
|
|
|
|
receiveAddresses.removeRange(0, receiveAddresses.length);
|
|
|
|
final newAdresses = addresses
|
|
|
|
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
|
|
|
receiveAddresses.addAll(newAdresses);
|
|
|
|
}
|
2022-01-12 14:32:56 +00:00
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
@action
|
|
|
|
void updateChangeAddresses() {
|
|
|
|
changeAddresses.removeRange(0, changeAddresses.length);
|
|
|
|
final newAdresses = addresses
|
|
|
|
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
|
|
|
changeAddresses.addAll(newAdresses);
|
2022-01-12 13:20:43 +00:00
|
|
|
}
|
2022-01-18 16:27:33 +00:00
|
|
|
|
|
|
|
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
|
|
|
|
var hasAddrUse = true;
|
|
|
|
List<BitcoinAddressRecord> addrs;
|
|
|
|
|
|
|
|
if (addresses.isNotEmpty) {
|
|
|
|
addrs = addresses
|
|
|
|
.where((addr) => addr.isHidden == isHidden)
|
|
|
|
.toList();
|
|
|
|
} else {
|
|
|
|
addrs = await _createNewAddresses(
|
|
|
|
isHidden
|
|
|
|
? defaultChangeAddressesCount
|
|
|
|
: defaultReceiveAddressesCount,
|
|
|
|
startIndex: 0,
|
|
|
|
hd: hd,
|
|
|
|
isHidden: isHidden);
|
|
|
|
}
|
|
|
|
|
|
|
|
while(hasAddrUse) {
|
|
|
|
final addr = addrs.last.address;
|
2022-01-24 12:04:23 +00:00
|
|
|
hasAddrUse = await _hasAddressUsed(addr);
|
2022-01-18 16:27:33 +00:00
|
|
|
|
|
|
|
if (!hasAddrUse) {
|
|
|
|
break;
|
|
|
|
}
|
2022-01-24 12:04:23 +00:00
|
|
|
|
2022-01-18 16:27:33 +00:00
|
|
|
final start = addrs.length;
|
|
|
|
final count = start + gap;
|
|
|
|
final batch = await _createNewAddresses(
|
|
|
|
count,
|
|
|
|
startIndex: start,
|
|
|
|
hd: hd,
|
|
|
|
isHidden: isHidden);
|
|
|
|
addrs.addAll(batch);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addresses.length < addrs.length) {
|
|
|
|
_addAddresses(addrs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _generateInitialAddresses() async {
|
|
|
|
var countOfReceiveAddresses = 0;
|
|
|
|
var countOfHiddenAddresses = 0;
|
|
|
|
|
|
|
|
addresses.forEach((addr) {
|
|
|
|
if (addr.isHidden) {
|
|
|
|
countOfHiddenAddresses += 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
countOfReceiveAddresses += 1;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
|
|
|
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
|
|
|
final newAddresses = await _createNewAddresses(
|
|
|
|
addressesCount,
|
|
|
|
startIndex: countOfReceiveAddresses,
|
|
|
|
hd: mainHd,
|
|
|
|
isHidden: false);
|
|
|
|
addresses.addAll(newAddresses);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
|
|
|
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
|
|
|
final newAddresses = await _createNewAddresses(
|
|
|
|
addressesCount,
|
|
|
|
startIndex: countOfHiddenAddresses,
|
|
|
|
hd: sideHd,
|
|
|
|
isHidden: true);
|
|
|
|
addresses.addAll(newAddresses);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
|
|
|
|
{int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async {
|
|
|
|
final list = <BitcoinAddressRecord>[];
|
|
|
|
|
|
|
|
for (var i = startIndex; i < count + startIndex; i++) {
|
|
|
|
final address = BitcoinAddressRecord(
|
|
|
|
getAddress(index: i, hd: hd),
|
|
|
|
index: i,
|
|
|
|
isHidden: isHidden);
|
|
|
|
list.add(address);
|
|
|
|
}
|
|
|
|
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
|
|
|
final addressesSet = this.addresses.toSet();
|
|
|
|
addressesSet.addAll(addresses);
|
|
|
|
this.addresses.removeRange(0, this.addresses.length);
|
|
|
|
this.addresses.addAll(addressesSet);
|
|
|
|
}
|
|
|
|
|
2022-01-24 12:04:23 +00:00
|
|
|
Future<bool> _hasAddressUsed(String address) async {
|
2022-01-18 16:27:33 +00:00
|
|
|
final sh = scriptHash(address, networkType: networkType);
|
2022-01-24 12:04:23 +00:00
|
|
|
final transactionHistory = await electrumClient.getHistory(sh);
|
|
|
|
return transactionHistory.isNotEmpty;
|
2022-01-18 16:27:33 +00:00
|
|
|
}
|
2021-12-24 12:52:08 +00:00
|
|
|
}
|