mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-27 22:19:41 +00:00
9754d67601
* add used addresses list * generate new address button * fix wallet type issue * fix addresses button title * update selectButton * show all wallet addresses * add tx amount and balance * fix ui * remove cashAddr format * fix generating new address issue * disable autogenerating * fix cashAddr format * minor fix * add search bar * Update address_cell.dart * fix merge conflict * address labeling feature * review fixes --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
299 lines
9.6 KiB
Dart
299 lines
9.6 KiB
Dart
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/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.electrumClient,
|
|
required this.networkType,
|
|
List<BitcoinAddressRecord>? initialAddresses,
|
|
int initialRegularAddressIndex = 0,
|
|
int initialChangeAddressIndex = 0})
|
|
: 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()),
|
|
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);
|
|
|
|
static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
|
|
|
|
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 {
|
|
if (isEnabledAutoGenerateSubaddress) {
|
|
if (receiveAddresses.isEmpty) {
|
|
final newAddress = generateNewAddress().address;
|
|
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
|
|
}
|
|
final receiveAddress = receiveAddresses.first.address;
|
|
|
|
return walletInfo.type == WalletType.bitcoinCash
|
|
? toCashAddr(receiveAddress)
|
|
: receiveAddress;
|
|
} else {
|
|
final receiveAddress = (receiveAddresses.first.address != addresses.first.address &&
|
|
previousAddressRecord != null)
|
|
? previousAddressRecord!.address
|
|
: addresses.first.address;
|
|
|
|
return walletInfo.type == WalletType.bitcoinCash
|
|
? toCashAddr(receiveAddress)
|
|
: receiveAddress;
|
|
}
|
|
}
|
|
|
|
@observable
|
|
bool isEnabledAutoGenerateSubaddress = true;
|
|
|
|
@override
|
|
set address(String addr) {
|
|
if (addr.startsWith('bitcoincash:')) {
|
|
addr = toLegacy(addr);
|
|
}
|
|
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
|
|
|
previousAddressRecord = addressRecord;
|
|
receiveAddresses.remove(addressRecord);
|
|
receiveAddresses.insert(0, addressRecord);
|
|
}
|
|
|
|
@override
|
|
String get primaryAddress => getAddress(index: 0, hd: mainHd);
|
|
|
|
int currentReceiveAddressIndex;
|
|
int currentChangeAddressIndex;
|
|
|
|
@observable
|
|
BitcoinAddressRecord? previousAddressRecord;
|
|
|
|
@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;
|
|
}
|
|
|
|
BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) {
|
|
final isHidden = hd == sideHd;
|
|
|
|
final newAddressIndex = addresses.fold(
|
|
0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc);
|
|
|
|
final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd),
|
|
index: newAddressIndex, isHidden: isHidden, name: label ?? '');
|
|
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());
|
|
}
|
|
}
|
|
|
|
@action
|
|
void updateAddress(String address, String label) {
|
|
if (address.startsWith('bitcoincash:')) {
|
|
address = toLegacy(address);
|
|
}
|
|
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address);
|
|
addressRecord.setNewName(label);
|
|
final index = addresses.indexOf(addressRecord);
|
|
addresses.remove(addressRecord);
|
|
addresses.insert(index, addressRecord);
|
|
}
|
|
|
|
@action
|
|
void updateReceiveAddresses() {
|
|
receiveAddresses.removeRange(0, receiveAddresses.length);
|
|
final newAddresses =
|
|
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
|
receiveAddresses.addAll(newAddresses);
|
|
}
|
|
|
|
@action
|
|
void updateChangeAddresses() {
|
|
changeAddresses.removeRange(0, changeAddresses.length);
|
|
final newAddresses =
|
|
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
|
changeAddresses.addAll(newAddresses);
|
|
}
|
|
|
|
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;
|
|
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);
|
|
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,
|
|
{required bitcoin.HDWallet hd, int startIndex = 0, 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);
|
|
}
|
|
|
|
Future<bool> _hasAddressUsed(String address) async {
|
|
final sh = scriptHash(address, networkType: networkType);
|
|
final transactionHistory = await electrumClient.getHistory(sh);
|
|
return transactionHistory.isNotEmpty;
|
|
}
|
|
}
|