feat: rescan a specific address

This commit is contained in:
Rafael Saes 2024-09-26 19:36:24 -03:00
parent fc7bea6830
commit bf0dcc8d7b
6 changed files with 85 additions and 10 deletions

View file

@ -63,6 +63,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
super.isUsed = false, super.isUsed = false,
required super.type, required super.type,
String? scriptHash, String? scriptHash,
this.spendKey,
required super.network, required super.network,
}) : scriptHash = scriptHash ?? }) : scriptHash = scriptHash ??
(network != null ? BitcoinAddressUtils.scriptHash(address, network: network) : null); (network != null ? BitcoinAddressUtils.scriptHash(address, network: network) : null);
@ -88,6 +89,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
} }
String? scriptHash; String? scriptHash;
ECPrivate? spendKey;
String getScriptHash(BasedUtxoNetwork network) { String getScriptHash(BasedUtxoNetwork network) {
if (scriptHash != null) return scriptHash!; if (scriptHash != null) return scriptHash!;

View file

@ -602,7 +602,11 @@ abstract class ElectrumWalletBase
spendsSilentPayment = true; spendsSilentPayment = true;
isSilentPayment = true; isSilentPayment = true;
} else if (!isHardwareWallet) { } else if (!isHardwareWallet) {
privkey = final spendKey = (utx.bitcoinAddressRecord as BitcoinAddressRecord).spendKey;
// if spend key is present, this is needed to disable tweaking the key during signing
isSilentPayment = spendKey != null;
privkey = spendKey ??
generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network); generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
} }
@ -1266,14 +1270,22 @@ abstract class ElectrumWalletBase
if (hasSilentPaymentsScanning) { if (hasSilentPaymentsScanning) {
// Update unspents stored from scanned silent payment transactions // Update unspents stored from scanned silent payment transactions
transactionHistory.transactions.values.forEach((tx) { transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null) { if (tx.unspents != null && tx.unspents!.isNotEmpty) {
updatedUnspentCoins.addAll(tx.unspents!); updatedUnspentCoins.addAll(tx.unspents!);
} }
}); });
} }
await Future.wait(walletAddresses.allAddresses.map((address) async { await Future.wait(walletAddresses.allAddresses.map((address) async {
updatedUnspentCoins.addAll(await fetchUnspent(address)); final unspentList = await fetchUnspent(address);
if (unspentList.isNotEmpty) {
for (final unspent in unspentList) {
if (!updatedUnspentCoins.any((element) => element.hash == unspent.hash)) {
updatedUnspentCoins.add(unspent);
}
}
}
})); }));
unspentCoins = updatedUnspentCoins; unspentCoins = updatedUnspentCoins;
@ -1860,6 +1872,12 @@ abstract class ElectrumWalletBase
} }
} }
Future<void> fullAddressUpdate(BitcoinAddressRecord address) async {
await updateUnspents(address);
await _fetchBalance(address);
await _fetchAddressHistory(address, await getCurrentChainTip());
}
Future<void> _subscribeForUpdates() async { Future<void> _subscribeForUpdates() async {
final unsubscribedScriptHashes = walletAddresses.allAddresses.where( final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)), (address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)),
@ -1871,11 +1889,7 @@ abstract class ElectrumWalletBase
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh]?.listen((event) async { _scripthashesUpdateSubject[sh]?.listen((event) async {
try { try {
await updateUnspents(address); await fullAddressUpdate(address);
await updateBalance();
await _fetchAddressHistory(address, await getCurrentChainTip());
} catch (e, s) { } catch (e, s) {
print(e.toString()); print(e.toString());
_onError?.call(FlutterErrorDetails( _onError?.call(FlutterErrorDetails(
@ -1888,6 +1902,20 @@ abstract class ElectrumWalletBase
})); }));
} }
Future<ElectrumBalance> _fetchBalance(BitcoinAddressRecord address) async {
final sh = address.getScriptHash(network);
final balance = await electrumClient.getBalance(sh);
final totalConfirmed = balance['confirmed'] as int? ?? 0;
final totalUnconfirmed = balance['unconfirmed'] as int? ?? 0;
if (totalConfirmed > 0 || totalUnconfirmed > 0) {
address.setAsUsed();
}
return ElectrumBalance(confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: 0);
}
Future<ElectrumBalance> _fetchBalances() async { Future<ElectrumBalance> _fetchBalances() async {
final addresses = walletAddresses.allAddresses.toList(); final addresses = walletAddresses.allAddresses.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[]; final balanceFutures = <Future<Map<String, dynamic>>>[];

View file

@ -588,7 +588,6 @@ class CWBitcoin extends Bitcoin {
} }
final updatedOutputs = outputs.map((output) { final updatedOutputs = outputs.map((output) {
try { try {
final pendingOut = pendingTx!.outputs[outputs.indexOf(output)]; final pendingOut = pendingTx!.outputs[outputs.indexOf(output)];
final updatedOutput = output; final updatedOutput = output;
@ -609,4 +608,31 @@ class CWBitcoin extends Bitcoin {
final tx = txInfo as ElectrumTransactionInfo; final tx = txInfo as ElectrumTransactionInfo;
return tx.isReceivedSilentPayment; return tx.isReceivedSilentPayment;
} }
@override
Future<void> fullAddressUpdate(Object wallet, String address) async {
final bitcoinWallet = wallet as ElectrumWallet;
final spAddressRecord =
bitcoinWallet.walletAddresses.silentAddresses.firstWhere((addr) => addr.address == address);
final bitcoinAddressRecord = BitcoinAddressRecord(
address,
index: spAddressRecord.index,
isHidden: spAddressRecord.isHidden,
txCount: spAddressRecord.txCount,
balance: spAddressRecord.balance,
name: spAddressRecord.name,
isUsed: spAddressRecord.isUsed,
type: spAddressRecord.type,
network: spAddressRecord.network,
spendKey: bitcoinWallet.walletAddresses.silentAddress!.b_spend.tweakAdd(
BigintUtils.fromBytes(
BytesUtils.fromHexString(spAddressRecord.silentPaymentTweak!),
),
),
);
bitcoinWallet.walletAddresses.addAddresses([bitcoinAddressRecord]);
bitcoinWallet.fullAddressUpdate(bitcoinAddressRecord);
}
} }

View file

@ -16,6 +16,7 @@ class AddressCell extends StatelessWidget {
this.onTap, this.onTap,
this.onEdit, this.onEdit,
this.onDelete, this.onDelete,
this.onRescan,
this.txCount, this.txCount,
this.balance, this.balance,
this.isChange = false, this.isChange = false,
@ -30,6 +31,7 @@ class AddressCell extends StatelessWidget {
bool hasBalance = false, bool hasBalance = false,
Function()? onEdit, Function()? onEdit,
Function()? onDelete, Function()? onDelete,
Function()? onRescan,
}) => }) =>
AddressCell( AddressCell(
address: item.address, address: item.address,
@ -41,6 +43,7 @@ class AddressCell extends StatelessWidget {
onTap: onTap, onTap: onTap,
onEdit: onEdit, onEdit: onEdit,
onDelete: onDelete, onDelete: onDelete,
onRescan: onRescan,
txCount: item.txCount, txCount: item.txCount,
balance: item.balance, balance: item.balance,
isChange: item.isChange, isChange: item.isChange,
@ -55,6 +58,7 @@ class AddressCell extends StatelessWidget {
final Function(String)? onTap; final Function(String)? onTap;
final Function()? onEdit; final Function()? onEdit;
final Function()? onDelete; final Function()? onDelete;
final Function()? onRescan;
final int? txCount; final int? txCount;
final String? balance; final String? balance;
final bool isChange; final bool isChange;
@ -89,7 +93,9 @@ class AddressCell extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Row( Row(
mainAxisAlignment: name.isNotEmpty ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center, mainAxisAlignment: name.isNotEmpty
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Row( Row(
@ -196,6 +202,14 @@ class AddressCell extends StatelessWidget {
icon: Icons.edit, icon: Icons.edit,
label: S.of(context).edit, label: S.of(context).edit,
), ),
if (onRescan != null)
SlidableAction(
onPressed: (_) => onRescan!.call(),
backgroundColor: Colors.green,
foregroundColor: Colors.white,
icon: Icons.refresh,
label: S.of(context).rescan,
),
if (onDelete != null) if (onDelete != null)
SlidableAction( SlidableAction(
onPressed: (_) => onDelete!.call(), onPressed: (_) => onDelete!.call(),

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
@ -102,6 +103,9 @@ class AddressList extends StatelessWidget {
onEdit: editable onEdit: editable
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item) ? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item)
: null, : null,
onRescan: (item.isOneTimeReceiveAddress ?? false)
? () => bitcoin!.fullAddressUpdate(addressListViewModel.wallet, item.address)
: null,
); );
}); });
} }

View file

@ -224,6 +224,7 @@ abstract class Bitcoin {
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs); List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
bool txIsReceivedSilentPayment(TransactionInfo txInfo); bool txIsReceivedSilentPayment(TransactionInfo txInfo);
Future<void> fullAddressUpdate(Object wallet, String address);
} }
"""; """;