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

View file

@ -602,7 +602,11 @@ abstract class ElectrumWalletBase
spendsSilentPayment = true;
isSilentPayment = true;
} 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);
}
@ -1266,14 +1270,22 @@ abstract class ElectrumWalletBase
if (hasSilentPaymentsScanning) {
// Update unspents stored from scanned silent payment transactions
transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null) {
if (tx.unspents != null && tx.unspents!.isNotEmpty) {
updatedUnspentCoins.addAll(tx.unspents!);
}
});
}
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;
@ -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 {
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)),
@ -1871,11 +1889,7 @@ abstract class ElectrumWalletBase
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh]?.listen((event) async {
try {
await updateUnspents(address);
await updateBalance();
await _fetchAddressHistory(address, await getCurrentChainTip());
await fullAddressUpdate(address);
} catch (e, s) {
print(e.toString());
_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 {
final addresses = walletAddresses.allAddresses.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[];

View file

@ -588,7 +588,6 @@ class CWBitcoin extends Bitcoin {
}
final updatedOutputs = outputs.map((output) {
try {
final pendingOut = pendingTx!.outputs[outputs.indexOf(output)];
final updatedOutput = output;
@ -609,4 +608,31 @@ class CWBitcoin extends Bitcoin {
final tx = txInfo as ElectrumTransactionInfo;
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.onEdit,
this.onDelete,
this.onRescan,
this.txCount,
this.balance,
this.isChange = false,
@ -30,6 +31,7 @@ class AddressCell extends StatelessWidget {
bool hasBalance = false,
Function()? onEdit,
Function()? onDelete,
Function()? onRescan,
}) =>
AddressCell(
address: item.address,
@ -41,6 +43,7 @@ class AddressCell extends StatelessWidget {
onTap: onTap,
onEdit: onEdit,
onDelete: onDelete,
onRescan: onRescan,
txCount: item.txCount,
balance: item.balance,
isChange: item.isChange,
@ -55,6 +58,7 @@ class AddressCell extends StatelessWidget {
final Function(String)? onTap;
final Function()? onEdit;
final Function()? onDelete;
final Function()? onRescan;
final int? txCount;
final String? balance;
final bool isChange;
@ -89,7 +93,9 @@ class AddressCell extends StatelessWidget {
child: Column(
children: [
Row(
mainAxisAlignment: name.isNotEmpty ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center,
mainAxisAlignment: name.isNotEmpty
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Row(
@ -196,6 +202,14 @@ class AddressCell extends StatelessWidget {
icon: Icons.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)
SlidableAction(
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/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
@ -102,6 +103,9 @@ class AddressList extends StatelessWidget {
onEdit: editable
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item)
: 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});
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
bool txIsReceivedSilentPayment(TransactionInfo txInfo);
Future<void> fullAddressUpdate(Object wallet, String address);
}
""";