Merge pull request #239 from cake-tech/transactions-bug

Transactions bug
This commit is contained in:
mkyq 2022-01-18 18:43:06 +02:00 committed by GitHub
commit 751d5066b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 377 additions and 151 deletions

View file

@ -1,14 +1,18 @@
import 'dart:convert'; import 'dart:convert';
class BitcoinAddressRecord { class BitcoinAddressRecord {
BitcoinAddressRecord(this.address, {this.index, bool isHidden}) BitcoinAddressRecord(this.address,
: _isHidden = isHidden; {this.index, this.isHidden = false, bool isUsed = false})
: _isUsed = isUsed;
factory BitcoinAddressRecord.fromJSON(String jsonSource) { factory BitcoinAddressRecord.fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(decoded['address'] as String, return BitcoinAddressRecord(
index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool); decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool ?? false,
isUsed: decoded['isUsed'] as bool ?? false);
} }
@override @override
@ -16,13 +20,21 @@ class BitcoinAddressRecord {
o is BitcoinAddressRecord && address == o.address; o is BitcoinAddressRecord && address == o.address;
final String address; final String address;
bool get isHidden => _isHidden ?? false; final bool isHidden;
int index; final int index;
final bool _isHidden; bool get isUsed => _isUsed;
@override @override
int get hashCode => address.hashCode; int get hashCode => address.hashCode;
bool _isUsed;
void setAsUsed() => _isUsed = true;
String toJSON() => String toJSON() =>
json.encode({'address': address, 'index': index, 'isHidden': isHidden}); json.encode({
'address': address,
'index': index,
'isHidden': isHidden,
'isUsed': isUsed});
} }

View file

@ -23,7 +23,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
@required Box<UnspentCoinsInfo> unspentCoinsInfo, @required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord> initialAddresses, List<BitcoinAddressRecord> initialAddresses,
ElectrumBalance initialBalance, ElectrumBalance initialBalance,
int accountIndex = 0}) int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super( : super(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
@ -34,8 +35,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance) { initialBalance: initialBalance) {
walletAddresses = BitcoinWalletAddresses( walletAddresses = BitcoinWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
accountIndex: accountIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd, mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed( sideHd: bitcoin.HDWallet.fromSeed(
mnemonicToSeedBytes(mnemonic), network: networkType) mnemonicToSeedBytes(mnemonic), network: networkType)
@ -58,6 +61,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialBalance: snp.balance, initialBalance: snp.balance,
accountIndex: snp.accountIndex); initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
} }
} }

View file

@ -1,4 +1,5 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -16,18 +17,21 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
BitcoinWalletAddressesBase( BitcoinWalletAddressesBase(
WalletInfo walletInfo, WalletInfo walletInfo,
{@required List<BitcoinAddressRecord> initialAddresses, {@required List<BitcoinAddressRecord> initialAddresses,
int accountIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0,
ElectrumClient electrumClient,
@required bitcoin.HDWallet mainHd, @required bitcoin.HDWallet mainHd,
@required bitcoin.HDWallet sideHd, @required bitcoin.HDWallet sideHd,
@required this.networkType}) @required bitcoin.NetworkType networkType})
: super( : super(
walletInfo, walletInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
accountIndex: accountIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd, mainHd: mainHd,
sideHd: sideHd); sideHd: sideHd,
electrumClient: electrumClient,
bitcoin.NetworkType networkType; networkType: networkType);
@override @override
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>

View file

@ -217,23 +217,16 @@ class ElectrumClient {
return <String, Object>{}; return <String, Object>{};
}); });
Future<Map<String, Object>> getTransactionExpanded( Future<String> getTransactionHex(
{@required String hash}) async { {@required String hash}) async =>
try { call(method: 'blockchain.transaction.get', params: [hash, false])
final originalTx = await getTransactionRaw(hash: hash); .then((dynamic result) {
final vins = originalTx['vin'] as List<Object>; if (result is String) {
return result;
for (dynamic vin in vins) {
if (vin is Map<String, Object>) {
vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String);
} }
}
return originalTx; return '';
} catch (_) { });
return {};
}
}
Future<String> broadcastTransaction( Future<String> broadcastTransaction(
{@required String transactionRaw}) async => {@required String transactionRaw}) async =>

View file

@ -1,3 +1,4 @@
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
@ -8,6 +9,34 @@ import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart'; import 'package:cw_core/format_amount.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
String addressFromOutput(Uint8List script) {
try {
return bitcoin.P2PKH(
data: PaymentData(output: script),
network: bitcoin.bitcoin)
.data
.address;
} catch (_) {}
try {
return bitcoin.P2WPKH(
data: PaymentData(output: script),
network: bitcoin.bitcoin)
.data
.address;
} catch(_) {}
return null;
}
class ElectrumTransactionBundle {
ElectrumTransactionBundle(this.originalTransaction, {this.ins, this.time, this.confirmations});
final bitcoin.Transaction originalTransaction;
final List<bitcoin.Transaction> ins;
final int time;
final int confirmations;
}
class ElectrumTransactionInfo extends TransactionInfo { class ElectrumTransactionInfo extends TransactionInfo {
ElectrumTransactionInfo(this.type, ElectrumTransactionInfo(this.type,
{@required String id, {@required String id,
@ -84,6 +113,49 @@ class ElectrumTransactionInfo extends TransactionInfo {
confirmations: confirmations); confirmations: confirmations);
} }
factory ElectrumTransactionInfo.fromElectrumBundle(
ElectrumTransactionBundle bundle, WalletType type,
{@required Set<String> addresses, int height}) {
final date = DateTime.fromMillisecondsSinceEpoch(bundle.time * 1000);
var direction = TransactionDirection.incoming;
var amount = 0;
var inputAmount = 0;
var totalOutAmount = 0;
for (var i = 0; i < bundle.originalTransaction.ins.length; i++) {
final input = bundle.originalTransaction.ins[i];
final inputTransaction = bundle.ins[i];
final vout = input.index;
final outTransaction = inputTransaction.outs[vout];
final address = addressFromOutput(outTransaction.script);
inputAmount += outTransaction.value;
if (addresses.contains(address)) {
direction = TransactionDirection.outgoing;
}
}
for (final out in bundle.originalTransaction.outs) {
totalOutAmount += out.value;
final address = addressFromOutput(out.script);
final addressExists = addresses.contains(address);
if ((direction == TransactionDirection.incoming && addressExists) ||
(direction == TransactionDirection.outgoing && !addressExists)) {
amount += out.value;
}
}
final fee = inputAmount - totalOutAmount;
return ElectrumTransactionInfo(type,
id: bundle.originalTransaction.getId(),
height: height,
isPending: false,
fee: fee,
direction: direction,
amount: amount,
date: date,
confirmations: bundle.confirmations);
}
factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex, factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
{List<String> addresses, int height, int timestamp, int confirmations}) { {List<String> addresses, int height, int timestamp, int confirmations}) {
final tx = bitcoin.Transaction.fromHex(hex); final tx = bitcoin.Transaction.fromHex(hex);

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -31,6 +32,7 @@ import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum.dart';
import 'package:hex/hex.dart';
part 'electrum_wallet.g.dart'; part 'electrum_wallet.g.dart';
@ -123,6 +125,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<void> startSync() async { Future<void> startSync() async {
try { try {
syncStatus = StartingSyncStatus(); syncStatus = StartingSyncStatus();
await walletAddresses.discoverAddresses();
await updateTransactions(); await updateTransactions();
_subscribeForUpdates(); _subscribeForUpdates();
await _updateBalance(); await _updateBalance();
@ -204,12 +207,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
amount = credentialsAmount; amount = credentialsAmount;
fee = calculateEstimatedFee(transactionCredentials.priority, amount, fee = calculateEstimatedFee(transactionCredentials.priority, amount,
outputsCount: outputs.length + 1); outputsCount: outputs.length + 1);
} else { } else {
final output = outputs.first; final output = outputs.first;
credentialsAmount = !output.sendAll credentialsAmount = !output.sendAll
? output.formattedCryptoAmount ? output.formattedCryptoAmount
: 0; : 0;
@ -221,7 +222,6 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
amount = output.sendAll || allAmount - credentialsAmount < minAmount amount = output.sendAll || allAmount - credentialsAmount < minAmount
? allAmount ? allAmount
: credentialsAmount; : credentialsAmount;
fee = output.sendAll || amount == allAmount fee = output.sendAll || amount == allAmount
? allAmountFee ? allAmountFee
: calculateEstimatedFee(transactionCredentials.priority, amount); : calculateEstimatedFee(transactionCredentials.priority, amount);
@ -238,7 +238,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
final txb = bitcoin.TransactionBuilder(network: networkType); final txb = bitcoin.TransactionBuilder(network: networkType);
final changeAddress = walletAddresses.addresses.last.address; final changeAddress = await walletAddresses.getChangeAddress();
var leftAmount = totalAmount; var leftAmount = totalAmount;
var totalInputAmount = 0; var totalInputAmount = 0;
@ -265,7 +265,6 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
txb.setVersion(1); txb.setVersion(1);
inputs.forEach((input) { inputs.forEach((input) {
if (input.isP2wpkh) { if (input.isP2wpkh) {
final p2wpkh = bitcoin final p2wpkh = bitcoin
@ -286,11 +285,9 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final outputAmount = hasMultiDestination final outputAmount = hasMultiDestination
? item.formattedCryptoAmount ? item.formattedCryptoAmount
: amount; : amount;
final outputAddress = item.isParsedAddress final outputAddress = item.isParsedAddress
? item.extractedAddress ? item.extractedAddress
: item.address; : item.address;
txb.addOutput( txb.addOutput(
addressToOutputScript(outputAddress, networkType), addressToOutputScript(outputAddress, networkType),
outputAmount); outputAmount);
@ -326,7 +323,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
String toJSON() => json.encode({ String toJSON() => json.encode({
'mnemonic': mnemonic, 'mnemonic': mnemonic,
'account_index': walletAddresses.accountIndex.toString(), 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance?.toJSON() 'balance': balance?.toJSON()
}); });
@ -479,11 +477,35 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
} }
Future<ElectrumTransactionBundle> getTransactionExpanded(
{@required String hash, @required int height}) async {
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
final transactionHex = verboseTransaction['hex'] as String;
final original = bitcoin.Transaction.fromHex(transactionHex);
final ins = <bitcoin.Transaction>[];
final time = verboseTransaction['time'] as int;
final confirmations = verboseTransaction['time'] as int;
for (final vin in original.ins) {
final id = HEX.encode(vin.hash.reversed.toList());
final txHex = await electrumClient.getTransactionHex(hash: id);
final tx = bitcoin.Transaction.fromHex(txHex);
ins.add(tx);
}
return ElectrumTransactionBundle(
original,
ins: ins,
time: time,
confirmations: confirmations);
}
Future<ElectrumTransactionInfo> fetchTransactionInfo( Future<ElectrumTransactionInfo> fetchTransactionInfo(
{@required String hash, @required int height}) async { {@required String hash, @required int height}) async {
final tx = await electrumClient.getTransactionExpanded(hash: hash); final tx = await getTransactionExpanded(hash: hash, height: height);
return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type, final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
height: height, addresses: walletAddresses.addresses); return ElectrumTransactionInfo.fromElectrumBundle(
tx,walletInfo.type, addresses: addresses, height: height);
} }
@override @override
@ -524,7 +546,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
scriptHashes.forEach((sh) async { scriptHashes.forEach((sh) async {
await _scripthashesUpdateSubject[sh]?.close(); await _scripthashesUpdateSubject[sh]?.close();
_scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh].listen((event) async { _scripthashesUpdateSubject[sh]?.listen((event) async {
try { try {
await _updateBalance(); await _updateBalance();
await updateUnspent(); await updateUnspent();
@ -537,16 +559,34 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
Future<ElectrumBalance> _fetchBalances() async { Future<ElectrumBalance> _fetchBalances() async {
final balances = await Future.wait( final addresses = walletAddresses.addresses.toList();
scriptHashes.map((sh) => electrumClient.getBalance(sh))); final balanceFutures = <Future<Map<String, dynamic>>>[];
final balance = balances.fold(
ElectrumBalance(confirmed: 0, unconfirmed: 0),
(ElectrumBalance acc, val) => ElectrumBalance(
confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
unconfirmed:
(val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
return balance; for (var i = 0; i < addresses.length; i++) {
final addressRecord = addresses[i];
final sh = scriptHash(addressRecord.address, networkType: networkType);
final balanceFuture = electrumClient.getBalance(sh);
balanceFutures.add(balanceFuture);
}
final balances = await Future.wait(balanceFutures);
var totalConfirmed = 0;
var totalUnconfirmed = 0;
for (var i = 0; i < balances.length; i++) {
final addressRecord = addresses[i];
final balance = balances[i];
final confirmed = balance['confirmed'] as int ?? 0;
final unconfirmed = balance['unconfirmed'] as int ?? 0;
totalConfirmed += confirmed;
totalUnconfirmed += unconfirmed;
if (confirmed > 0 || unconfirmed > 0) {
addressRecord.setAsUsed();
}
}
return ElectrumBalance(confirmed: totalConfirmed, unconfirmed: totalUnconfirmed);
} }
Future<void> _updateBalance() async { Future<void> _updateBalance() async {

View file

@ -1,5 +1,7 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_address_record.dart'; 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_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -14,120 +16,118 @@ class ElectrumWalletAddresses = ElectrumWalletAddressesBase
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
ElectrumWalletAddressesBase(WalletInfo walletInfo, ElectrumWalletAddressesBase(WalletInfo walletInfo,
{@required List<BitcoinAddressRecord> initialAddresses, {@required List<BitcoinAddressRecord> initialAddresses,
int accountIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0,
this.mainHd, this.mainHd,
this.sideHd}) this.sideHd,
this.electrumClient,
this.networkType})
: super(walletInfo) { : super(walletInfo) {
this.accountIndex = accountIndex; currentReceiveAddressIndex = initialRegularAddressIndex;
currentChangeAddressIndex = initialChangeAddressIndex;
addresses = ObservableList<BitcoinAddressRecord>.of( addresses = ObservableList<BitcoinAddressRecord>.of(
(initialAddresses ?? []).toSet()); (initialAddresses ?? []).toSet());
} }
static const regularAddressesCount = 22; static const defaultReceiveAddressesCount = 22;
static const hiddenAddressesCount = 17; static const defaultChangeAddressesCount = 17;
static const gap = 20;
@override @override
@observable @observable
String address; String address;
int currentReceiveAddressIndex;
int currentChangeAddressIndex;
ElectrumClient electrumClient;
bitcoin.NetworkType networkType;
bitcoin.HDWallet mainHd; bitcoin.HDWallet mainHd;
bitcoin.HDWallet sideHd; bitcoin.HDWallet sideHd;
ObservableList<BitcoinAddressRecord> addresses; ObservableList<BitcoinAddressRecord> addresses;
List<BitcoinAddressRecord> get availableAddresses => addresses List<BitcoinAddressRecord> get receiveAddresses => addresses
.where((addr) => !addr.isHidden) .where((addr) => !addr.isHidden && !addr.isUsed)
.toList(); .toList();
int accountIndex; List<BitcoinAddressRecord> get changeAddresses => addresses
.where((addr) => addr.isHidden && !addr.isUsed)
.toList();
Future<void> discoverAddresses() async {
await _discoverAddresses(mainHd, false);
await _discoverAddresses(sideHd, true);
await updateAddressesInBox();
}
@override @override
Future<void> init() async { Future<void> init() async {
await generateAddresses(); await _generateInitialAddresses();
final _availableAddresses = availableAddresses;
if (accountIndex >= _availableAddresses.length) { if (receiveAddresses.isEmpty) {
accountIndex = 0; final count = currentReceiveAddressIndex + gap;
final newAddresses = await _createNewAddresses(
count,
hd: mainHd,
startIndex: currentReceiveAddressIndex,
isHidden: false);
_addAddresses(newAddresses);
} else if (currentReceiveAddressIndex >= receiveAddresses.length) {
currentReceiveAddressIndex = 0;
} }
address = _availableAddresses[accountIndex].address; address = receiveAddresses[currentReceiveAddressIndex].address;
await updateAddressesInBox(); await updateAddressesInBox();
} }
@action @action
Future<void> nextAddress() async { Future<void> nextReceiveAddress() async {
accountIndex += 1; if (receiveAddresses.isEmpty) {
final _availableAddresses = availableAddresses; final count = currentReceiveAddressIndex + gap;
final newAddresses = await _createNewAddresses(
if (accountIndex >= _availableAddresses.length) { count,
accountIndex = 0; hd: sideHd,
startIndex: currentReceiveAddressIndex,
isHidden: false);
_addAddresses(newAddresses);
} else if (currentReceiveAddressIndex >= receiveAddresses.length) {
currentReceiveAddressIndex = 0;
} }
address = _availableAddresses[accountIndex].address; address = receiveAddresses[currentReceiveAddressIndex].address;
currentReceiveAddressIndex += 1;
await updateAddressesInBox(); await updateAddressesInBox();
} }
Future<void> generateAddresses() async { @action
final regularAddresses = <BitcoinAddressRecord>[]; Future<String> getChangeAddress() async {
final hiddenAddresses = <BitcoinAddressRecord>[]; if (changeAddresses.isEmpty) {
final count = currentChangeAddressIndex + gap;
addresses.forEach((addr) { final newAddresses = await _createNewAddresses(
if (addr.isHidden) { count,
hiddenAddresses.add(addr); startIndex: currentChangeAddressIndex,
return; isHidden: true);
} _addAddresses(newAddresses);
} else if (currentChangeAddressIndex >= changeAddresses.length) {
regularAddresses.add(addr); currentChangeAddressIndex = 0;
});
if (regularAddresses.length < regularAddressesCount) {
final addressesCount = regularAddressesCount - regularAddresses.length;
await generateNewAddresses(addressesCount,
startIndex: regularAddresses.length, hd: mainHd, isHidden: false);
} }
if (hiddenAddresses.length < hiddenAddressesCount) {
final addressesCount = hiddenAddressesCount - hiddenAddresses.length; final address = changeAddresses[currentChangeAddressIndex].address;
await generateNewAddresses(addressesCount, currentChangeAddressIndex += 1;
startIndex: hiddenAddresses.length, hd: sideHd, isHidden: true); return address;
}
} }
Future<BitcoinAddressRecord> generateNewAddress( Future<BitcoinAddressRecord> generateNewAddress(
{bool isHidden = false, bitcoin.HDWallet hd}) async { {bool isHidden = false, bitcoin.HDWallet hd}) async {
accountIndex += 1; currentReceiveAddressIndex += 1;
final address = BitcoinAddressRecord( final address = BitcoinAddressRecord(
getAddress(index: accountIndex, hd: hd), getAddress(index: currentReceiveAddressIndex, hd: hd),
index: accountIndex, index: currentReceiveAddressIndex,
isHidden: isHidden); isHidden: isHidden);
addresses.add(address); addresses.add(address);
return address; return address;
} }
Future<List<BitcoinAddressRecord>> generateNewAddresses(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);
}
addresses.addAll(list);
return list;
}
/*Future<void> updateAddress(String address) async {
for (final addr in addresses) {
if (addr.address == address) {
await save();
break;
}
}
}*/
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => ''; String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
@override @override
@ -135,7 +135,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
try { try {
addressesMap.clear(); addressesMap.clear();
addressesMap[address] = ''; addressesMap[address] = '';
await saveAddressesInBox(); await saveAddressesInBox();
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
@ -155,4 +154,107 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
address = availableAddresses[random.nextInt(availableAddresses.length)].address; address = availableAddresses[random.nextInt(availableAddresses.length)].address;
} }
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 _validateAddressUsing(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,
{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);
}
Future<bool> _validateAddressUsing(String address) async {
final sh = scriptHash(address, networkType: networkType);
final balance = await electrumClient.getBalance(sh);
return balance.isEmpty;
}
} }

View file

@ -15,7 +15,8 @@ class ElectrumWallletSnapshot {
String mnemonic; String mnemonic;
List<BitcoinAddressRecord> addresses; List<BitcoinAddressRecord> addresses;
ElectrumBalance balance; ElectrumBalance balance;
int accountIndex; int regularAddressIndex;
int changeAddressIndex;
Future<void> load() async { Future<void> load() async {
try { try {
@ -30,10 +31,12 @@ class ElectrumWallletSnapshot {
.toList(); .toList();
balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0); ElectrumBalance(confirmed: 0, unconfirmed: 0);
accountIndex = 0; regularAddressIndex = 0;
changeAddressIndex = 0;
try { try {
accountIndex = int.parse(data['account_index'] as String); regularAddressIndex = int.parse(data['account_index'] as String);
changeAddressIndex = int.parse(data['change_address_index'] as String);
} catch (_) {} } catch (_) {}
} catch (e) { } catch (e) {
print(e); print(e);

View file

@ -26,7 +26,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@required Box<UnspentCoinsInfo> unspentCoinsInfo, @required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord> initialAddresses, List<BitcoinAddressRecord> initialAddresses,
ElectrumBalance initialBalance, ElectrumBalance initialBalance,
int accountIndex = 0}) int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super( : super(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
@ -37,8 +38,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance) { initialBalance: initialBalance) {
walletAddresses = LitecoinWalletAddresses( walletAddresses = LitecoinWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
accountIndex: accountIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd, mainHd: hd,
sideHd: bitcoin.HDWallet sideHd: bitcoin.HDWallet
.fromSeed(mnemonicToSeedBytes(mnemonic), network: networkType) .fromSeed(mnemonicToSeedBytes(mnemonic), network: networkType)
@ -61,7 +64,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialBalance: snp.balance, initialBalance: snp.balance,
accountIndex: snp.accountIndex); initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
} }
@override @override

View file

@ -1,4 +1,5 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -16,32 +17,23 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
LitecoinWalletAddressesBase( LitecoinWalletAddressesBase(
WalletInfo walletInfo, WalletInfo walletInfo,
{@required List<BitcoinAddressRecord> initialAddresses, {@required List<BitcoinAddressRecord> initialAddresses,
int accountIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0,
ElectrumClient electrumClient,
@required bitcoin.HDWallet mainHd, @required bitcoin.HDWallet mainHd,
@required bitcoin.HDWallet sideHd, @required bitcoin.HDWallet sideHd,
@required this.networkType}) @required bitcoin.NetworkType networkType})
: super( : super(
walletInfo, walletInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
accountIndex: accountIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd, mainHd: mainHd,
sideHd: sideHd); sideHd: sideHd,
electrumClient: electrumClient,
bitcoin.NetworkType networkType; networkType: networkType);
@override @override
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
@override
Future<void> generateAddresses() async {
if (addresses.length < 33) {
final addressesCount = 22 - addresses.length;
await generateNewAddresses(addressesCount,
hd: mainHd, startIndex: addresses.length);
await generateNewAddresses(11,
startIndex: 0, hd: sideHd, isHidden: true);
}
}
} }

View file

@ -57,7 +57,7 @@ class CWBitcoin extends Bitcoin {
@override @override
Future<void> nextAddress(Object wallet) { Future<void> nextAddress(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
bitcoinWallet.walletAddresses.nextAddress(); bitcoinWallet.walletAddresses.nextReceiveAddress();
} }
@override @override

View file

@ -17,8 +17,8 @@ MONERO_COM_BUILD_NUMBER=6
MONERO_COM_BUNDLE_ID="com.cakewallet.monero" MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.3.1" CAKEWALLET_VERSION="4.3.4"
CAKEWALLET_BUILD_NUMBER=73 CAKEWALLET_BUILD_NUMBER=75
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_IOS_TYPE} " ]]; then if ! [[ " ${TYPES[*]} " =~ " ${APP_IOS_TYPE} " ]]; then