cake_wallet/cw_bitcoin/lib/electrum_wallet_addresses.dart
Serhii 66301ff247
CW-432-Add-Bitcoin-Cash-BCH (#1041)
* initial commit

* creating and restoring a wallet

* [skip ci] add transaction priority

* fix send and unspent screen

* fix transaction priority type

* replace Unspend with BitcoinUnspent

* add transaction creation

* fix transaction details screen

* minor fix

* fix create side wallet

* basic transaction creation flow

* fix fiat amount calculation

* edit wallet

* minor fix

* fix address book parsing

* merge commit fixes

* minor fixes

* Update gradle.properties

* fix bch unspent coins

* minor fix

* fix BitcoinCashTransactionPriority

* Fetch tags first before switching to one of them

* Update build_haven.sh

* Update build_haven.sh

* Update build_haven.sh

* Update build_haven.sh

* update transaction build function

* Update build_haven.sh

* add ability to rename and delete

* fix address format

* Update pubspec.lock

* Revert "fix address format"

This reverts commit 1549bf4d8c.

* fix address format for exange

* restore from qr

* Update configure.dart

* [skip ci] minor fix

* fix default fee rate

* Update onramper_buy_provider.dart

* Update wallet_address_list_view_model.dart

* PR comments fixes

* Update exchange_view_model.dart

* fix merge conflict

* Update address_validator.dart

* merge fixes

* update initialMigrationVersion

* move cw_bitbox to Cake tech

* PR fixes

* PR fixes

* Fix configure.dart brackets

* update the new version text after macos 

* dummy change to run workflow

* Fix Nano restore from QR issue
Fix Conflicts with main

* PR fixes

* Update app_config.sh 

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2023-10-13 01:50:16 +03:00

278 lines
No EOL
8.3 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.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);
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 (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
set address(String addr) => null;
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;
}
BitcoinAddressRecord generateNewAddress(
{bitcoin.HDWallet? hd, bool isHidden = false}) {
currentReceiveAddressIndex += 1;
// FIX-ME: Check logic for whichi HD should be used here ???
final address = BitcoinAddressRecord(
getAddress(index: currentReceiveAddressIndex, hd: hd ?? sideHd),
index: currentReceiveAddressIndex,
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());
}
}
@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);
}
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;
}
}