mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-24 12:39:37 +00:00
340 lines
11 KiB
Dart
340 lines
11 KiB
Dart
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|
import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
|
import 'package:cw_bitcoin/script_hash.dart';
|
|
import 'package:cw_core/wallet_addresses.dart';
|
|
import 'package:cw_core/wallet_info.dart';
|
|
import 'package:cw_core/wallet_type.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 this.mainHd,
|
|
required this.sideHd,
|
|
required this.transactionHistory,
|
|
required this.networkType,
|
|
List<BitcoinAddressRecord>? initialAddresses,
|
|
int initialRegularAddressIndex = 0,
|
|
int initialChangeAddressIndex = 0,
|
|
bitcoin.SilentPaymentReceiver? silentAddress,
|
|
}) : addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
|
silentAddress = silentAddress,
|
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
|
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
|
.toSet()),
|
|
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
|
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
|
.toSet()),
|
|
silentAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
|
.where((addressRecord) => addressRecord.silentAddressLabel != null)
|
|
.toSet()),
|
|
currentReceiveAddressIndex = initialRegularAddressIndex,
|
|
currentChangeAddressIndex = initialChangeAddressIndex,
|
|
super(walletInfo);
|
|
|
|
static const defaultReceiveAddressesCount = 22;
|
|
static const defaultChangeAddressesCount = 17;
|
|
static const gap = 20;
|
|
|
|
static String toCashAddr(String address) => bitbox.Address.toCashAddress(address);
|
|
|
|
final ObservableList<BitcoinAddressRecord> addresses;
|
|
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
|
final ObservableList<BitcoinAddressRecord> silentAddresses;
|
|
final ElectrumTransactionHistory transactionHistory;
|
|
final bitcoin.NetworkType networkType;
|
|
final bitcoin.HDWallet mainHd;
|
|
final bitcoin.HDWallet sideHd;
|
|
|
|
// TODO: labels -> disable edit on receive page
|
|
final bitcoin.SilentPaymentReceiver? silentAddress;
|
|
|
|
@observable
|
|
// ignore: prefer_final_fields
|
|
dynamic _addressPageType = bitcoin.AddressType.p2wpkh;
|
|
@computed
|
|
dynamic get addressPageType => _addressPageType;
|
|
|
|
@observable
|
|
String? activeAddress;
|
|
|
|
@computed
|
|
String get receiveAddress {
|
|
if (receiveAddresses.isEmpty) {
|
|
final address = generateNewAddress().address;
|
|
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address;
|
|
}
|
|
final receiveAddress = receiveAddresses.first.address;
|
|
|
|
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
|
|
}
|
|
|
|
@override
|
|
@computed
|
|
String get address {
|
|
if (addressPageType == bitcoin.AddressType.p2sp) {
|
|
return silentAddress!.toString();
|
|
}
|
|
|
|
if (activeAddress != null) {
|
|
return activeAddress!;
|
|
}
|
|
|
|
if (receiveAddresses.isEmpty) {
|
|
return generateNewAddress().address;
|
|
}
|
|
|
|
try {
|
|
return receiveAddresses
|
|
.firstWhere((address) => addressPageType == bitcoin.AddressType.p2wpkh
|
|
? address.type == null || address.type == addressPageType
|
|
: address.type == addressPageType)
|
|
.address;
|
|
} catch (_) {}
|
|
|
|
return receiveAddresses.first.address;
|
|
}
|
|
|
|
@override
|
|
set address(String addr) => activeAddress = addr;
|
|
|
|
int currentReceiveAddressIndex;
|
|
int currentChangeAddressIndex;
|
|
|
|
@computed
|
|
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
|
|
if (!addressRecord.isHidden) {
|
|
return acc + 1;
|
|
}
|
|
return acc;
|
|
});
|
|
|
|
@computed
|
|
int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
|
|
if (addressRecord.isHidden) {
|
|
return acc + 1;
|
|
}
|
|
return acc;
|
|
});
|
|
|
|
Future<void> discoverAddresses() async {
|
|
await _discoverAddresses(mainHd, false);
|
|
await _discoverAddresses(sideHd, true);
|
|
await updateAddressesInBox();
|
|
}
|
|
|
|
@override
|
|
Future<void> init() async {
|
|
await _generateInitialAddresses();
|
|
updateReceiveAddresses();
|
|
updateChangeAddresses();
|
|
await updateAddressesInBox();
|
|
|
|
if (currentReceiveAddressIndex >= receiveAddresses.length) {
|
|
currentReceiveAddressIndex = 0;
|
|
}
|
|
|
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
|
currentChangeAddressIndex = 0;
|
|
}
|
|
}
|
|
|
|
@action
|
|
Future<String> getChangeAddress() async {
|
|
updateChangeAddresses();
|
|
|
|
if (changeAddresses.isEmpty) {
|
|
final newAddresses = await _createNewAddresses(gap,
|
|
hd: sideHd,
|
|
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
|
|
isHidden: true);
|
|
addAddresses(newAddresses);
|
|
}
|
|
|
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
|
currentChangeAddressIndex = 0;
|
|
}
|
|
|
|
updateChangeAddresses();
|
|
final address = changeAddresses[currentChangeAddressIndex].address;
|
|
currentChangeAddressIndex += 1;
|
|
return address;
|
|
}
|
|
|
|
@action
|
|
BitcoinAddressRecord generateNewAddress(
|
|
{bitcoin.HDWallet? hd, bool isHidden = false, String? label}) {
|
|
if (label != null && silentAddress != null) {
|
|
final address = BitcoinAddressRecord(
|
|
bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress(
|
|
silentAddress!.scanPubkey,
|
|
silentAddress!.spendPubkey,
|
|
'0000000000000000000000000000000000000000000000000000000000000002'.fromHex,
|
|
hrp: silentAddress!.hrp,
|
|
version: silentAddress!.version)
|
|
.toString(),
|
|
index: currentReceiveAddressIndex,
|
|
isHidden: isHidden,
|
|
silentAddressLabel: label);
|
|
silentAddresses.add(address);
|
|
|
|
return address;
|
|
}
|
|
|
|
// FIX-ME: Check logic for whichi HD should be used here ???
|
|
final address = BitcoinAddressRecord(
|
|
getAddress(
|
|
index: currentReceiveAddressIndex,
|
|
hd: hd ?? sideHd,
|
|
addressType: addressPageType as bitcoin.AddressType,
|
|
),
|
|
index: currentReceiveAddressIndex,
|
|
isHidden: isHidden);
|
|
addresses.add(address);
|
|
return address;
|
|
|
|
currentReceiveAddressIndex += 1;
|
|
}
|
|
|
|
String getAddress(
|
|
{required int index, required bitcoin.HDWallet hd, bitcoin.AddressType? addressType}) =>
|
|
'';
|
|
|
|
@override
|
|
Future<void> updateAddressesInBox() async {
|
|
try {
|
|
addressesMap.clear();
|
|
addressesMap[address] = '';
|
|
await saveAddressesInBox();
|
|
} catch (e) {
|
|
print(e.toString());
|
|
}
|
|
}
|
|
|
|
@action
|
|
void updateReceiveAddresses() {
|
|
receiveAddresses.removeRange(0, receiveAddresses.length);
|
|
final newAdresses =
|
|
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
|
receiveAddresses.addAll(newAdresses);
|
|
}
|
|
|
|
@action
|
|
void updateChangeAddresses() {
|
|
changeAddresses.removeRange(0, changeAddresses.length);
|
|
final newAdresses =
|
|
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
|
changeAddresses.addAll(newAdresses);
|
|
}
|
|
|
|
@action
|
|
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden,
|
|
{bitcoin.AddressType? addressType}) async {
|
|
var hasAddrUse = true;
|
|
List<BitcoinAddressRecord> addrs;
|
|
|
|
if (addresses.where((addr) => addr.type == addressPageType).isNotEmpty) {
|
|
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
|
|
} else {
|
|
addrs = await _createNewAddresses(
|
|
isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
|
|
startIndex: 0,
|
|
hd: hd,
|
|
isHidden: isHidden,
|
|
addressType: addressType);
|
|
}
|
|
|
|
while (hasAddrUse) {
|
|
final addr = addrs.last.address;
|
|
hasAddrUse = await _hasAddressUsed(addr);
|
|
|
|
if (!hasAddrUse) {
|
|
break;
|
|
}
|
|
|
|
final start = addrs.length;
|
|
final count = start + gap;
|
|
final batch = await _createNewAddresses(count,
|
|
startIndex: start, hd: hd, isHidden: isHidden, addressType: addressType);
|
|
addrs.addAll(batch);
|
|
}
|
|
|
|
if (addresses.length < addrs.length || addressPageType != null) {
|
|
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,
|
|
{required bitcoin.HDWallet hd,
|
|
int startIndex = 0,
|
|
bool isHidden = false,
|
|
bitcoin.AddressType? addressType}) async {
|
|
final list = <BitcoinAddressRecord>[];
|
|
|
|
for (var i = startIndex; i < count + startIndex; i++) {
|
|
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd, addressType: addressType),
|
|
index: i, isHidden: isHidden, type: addressType);
|
|
list.add(address);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
@action
|
|
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
|
final addressesSet = this.addresses.toSet();
|
|
addressesSet.addAll(addresses);
|
|
this.addresses.removeRange(0, this.addresses.length);
|
|
this.addresses.addAll(addressesSet);
|
|
}
|
|
|
|
Future<bool> _hasAddressUsed(String address) async {
|
|
return transactionHistory.transactions.values.any((txInfo) => txInfo.to == address);
|
|
}
|
|
|
|
@override
|
|
@action
|
|
Future<void> setAddressType(dynamic type) async {
|
|
_addressPageType = type as bitcoin.AddressType;
|
|
|
|
if (addressPageType != bitcoin.AddressType.p2sp) {
|
|
await _discoverAddresses(mainHd, false, addressType: addressPageType as bitcoin.AddressType);
|
|
updateReceiveAddresses();
|
|
}
|
|
}
|
|
}
|