fix: sync, storing silent unspents

This commit is contained in:
Rafael Saes 2024-03-01 20:38:53 -03:00
parent 0016d436f3
commit 18697fbd0f
13 changed files with 192 additions and 140 deletions

View file

@ -124,10 +124,9 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
super.balance = 0, super.balance = 0,
super.name = '', super.name = '',
super.isUsed = false, super.isUsed = false,
required super.type,
required this.silentPaymentTweak, required this.silentPaymentTweak,
required super.network, required super.network,
}); }) : super(type: SilentPaymentsAddresType.p2sp);
factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource, factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource,
{BasedUtxoNetwork? network}) { {BasedUtxoNetwork? network}) {
@ -141,18 +140,14 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
txCount: decoded['txCount'] as int? ?? 0, txCount: decoded['txCount'] as int? ?? 0,
name: decoded['name'] as String? ?? '', name: decoded['name'] as String? ?? '',
balance: decoded['balance'] as int? ?? 0, balance: decoded['balance'] as int? ?? 0,
type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String)
: SegwitAddresType.p2wpkh,
network: (decoded['network'] as String?) == null network: (decoded['network'] as String?) == null
? network ? network
: BasedUtxoNetwork.fromName(decoded['network'] as String), : BasedUtxoNetwork.fromName(decoded['network'] as String),
silentPaymentTweak: decoded['silentPaymentTweak'] as String, silentPaymentTweak: decoded['silent_payment_tweak'] as String?,
); );
} }
final String silentPaymentTweak; final String? silentPaymentTweak;
@override @override
String toJSON() => json.encode({ String toJSON() => json.encode({
@ -165,6 +160,6 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
'balance': balance, 'balance': balance,
'type': type.toString(), 'type': type.toString(),
'network': network?.value, 'network': network?.value,
'silentPaymentTweak': silentPaymentTweak, 'silent_payment_tweak': silentPaymentTweak,
}); });
} }

View file

@ -1,10 +1,9 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/unspent_transaction_output.dart';
class BitcoinUnspent extends Unspent { class BitcoinUnspent extends Unspent {
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout, BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout,
{this.silentPaymentTweak, this.type}) {this.silentPaymentTweak})
: bitcoinAddressRecord = addressRecord, : bitcoinAddressRecord = addressRecord,
super(addressRecord.address, hash, value, vout, null); super(addressRecord.address, hash, value, vout, null);
@ -15,9 +14,6 @@ class BitcoinUnspent extends Unspent {
json['value'] as int, json['value'] as int,
json['tx_pos'] as int, json['tx_pos'] as int,
silentPaymentTweak: json['silent_payment_tweak'] as String?, silentPaymentTweak: json['silent_payment_tweak'] as String?,
type: json['type'] == null
? null
: BitcoinAddressType.values.firstWhere((e) => e.toString() == json['type']),
); );
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -27,12 +23,10 @@ class BitcoinUnspent extends Unspent {
'value': value, 'value': value,
'tx_pos': vout, 'tx_pos': vout,
'silent_payment_tweak': silentPaymentTweak, 'silent_payment_tweak': silentPaymentTweak,
'type': type.toString(),
}; };
return json; return json;
} }
final BaseBitcoinAddressRecord bitcoinAddressRecord; final BaseBitcoinAddressRecord bitcoinAddressRecord;
String? silentPaymentTweak; String? silentPaymentTweak;
BitcoinAddressType? type;
} }

View file

@ -32,7 +32,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0, int initialSilentAddressIndex = 0,
SilentPaymentOwner? silentAddress,
}) : super( }) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
@ -54,10 +53,13 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
initialSilentAddresses: initialSilentAddresses, initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex, initialSilentAddressIndex: initialSilentAddressIndex,
silentAddress: silentAddress,
mainHd: hd, mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"), sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
network: networkParam ?? network, network: networkParam ?? network,
masterHd: bitcoin.HDWallet.fromSeed(
seedBytes,
network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
),
); );
hasSilentPaymentsScanning = addressPageType == SilentPaymentsAddresType.p2sp.toString(); hasSilentPaymentsScanning = addressPageType == SilentPaymentsAddresType.p2sp.toString();
@ -97,16 +99,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses, initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex, initialSilentAddressIndex: initialSilentAddressIndex,
silentAddress: await SilentPaymentOwner.fromPrivateKeys(
b_scan: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SCAN_PATH).privKey!),
b_spend: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SPEND_PATH).privKey!),
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'),
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
@ -134,16 +126,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialSilentAddresses: snp.silentAddresses, initialSilentAddresses: snp.silentAddresses,
initialSilentAddressIndex: snp.silentAddressIndex, initialSilentAddressIndex: snp.silentAddressIndex,
silentAddress: await SilentPaymentOwner.fromPrivateKeys(
b_scan: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SCAN_PATH).privKey!),
b_spend: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SPEND_PATH).privKey!),
hrp: snp.network == BitcoinNetwork.testnet ? 'tsp' : 'sp'),
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: seedBytes, seedBytes: seedBytes,
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,

View file

@ -20,7 +20,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
super.initialChangeAddressIndex, super.initialChangeAddressIndex,
super.initialSilentAddresses, super.initialSilentAddresses,
super.initialSilentAddressIndex = 0, super.initialSilentAddressIndex = 0,
super.silentAddress, super.masterHd,
}) : super(walletInfo); }) : super(walletInfo);
@override @override

View file

@ -36,8 +36,8 @@ class ElectrumClient {
_tasks = {}, _tasks = {},
unterminatedString = ''; unterminatedString = '';
static const connectionTimeout = Duration(seconds: 5); static const connectionTimeout = Duration(seconds: 300);
static const aliveTimerDuration = Duration(seconds: 4); static const aliveTimerDuration = Duration(seconds: 300);
bool get isConnected => _isConnected; bool get isConnected => _isConnected;
Socket? socket; Socket? socket;

View file

@ -11,13 +11,11 @@ part 'electrum_transaction_history.g.dart';
const transactionsHistoryFileName = 'transactions.json'; const transactionsHistoryFileName = 'transactions.json';
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory;
with _$ElectrumTransactionHistory;
abstract class ElectrumTransactionHistoryBase abstract class ElectrumTransactionHistoryBase
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store { extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
ElectrumTransactionHistoryBase( ElectrumTransactionHistoryBase({required this.walletInfo, required String password})
{required this.walletInfo, required String password})
: _password = password, : _password = password,
_height = 0 { _height = 0 {
transactions = ObservableMap<String, ElectrumTransactionInfo>(); transactions = ObservableMap<String, ElectrumTransactionInfo>();
@ -30,8 +28,7 @@ abstract class ElectrumTransactionHistoryBase
Future<void> init() async => await _load(); Future<void> init() async => await _load();
@override @override
void addOne(ElectrumTransactionInfo transaction) => void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
transactions[transaction.id] = transaction;
@override @override
void addMany(Map<String, ElectrumTransactionInfo> transactions) => void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
@ -40,11 +37,13 @@ abstract class ElectrumTransactionHistoryBase
@override @override
Future<void> save() async { Future<void> save() async {
try { try {
final dirPath = final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName'; final path = '$dirPath/$transactionsHistoryFileName';
final data = final txjson = {};
json.encode({'height': _height, 'transactions': transactions}); for (final tx in transactions.entries) {
txjson[tx.key] = tx.value.toJson();
}
final data = json.encode({'height': _height, 'transactions': txjson});
await writeData(path: path, password: _password, data: data); await writeData(path: path, password: _password, data: data);
} catch (e) { } catch (e) {
print('Error while save bitcoin transaction history: ${e.toString()}'); print('Error while save bitcoin transaction history: ${e.toString()}');
@ -57,8 +56,7 @@ abstract class ElectrumTransactionHistoryBase
} }
Future<Map<String, dynamic>> _read() async { Future<Map<String, dynamic>> _read() async {
final dirPath = final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName'; final path = '$dirPath/$transactionsHistoryFileName';
final content = await read(path: path, password: _password); final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, dynamic>; return json.decode(content) as Map<String, dynamic>;
@ -84,7 +82,5 @@ abstract class ElectrumTransactionHistoryBase
} }
} }
void _update(ElectrumTransactionInfo transaction) => void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
transactions[transaction.id] = transaction;
} }

View file

@ -160,11 +160,11 @@ class ElectrumTransactionInfo extends TransactionInfo {
isPending: data['isPending'] as bool, isPending: data['isPending'] as bool,
confirmations: data['confirmations'] as int, confirmations: data['confirmations'] as int,
to: data['to'] as String?, to: data['to'] as String?,
unspents: data['unspent'] != null unspents: data['unspents'] != null
? (data['unspent'] as List<dynamic>) ? (data['unspents'] as List<dynamic>)
.map((unspent) => BitcoinUnspent.fromJSON( .map((unspent) => BitcoinUnspent.fromJSON(
BitcoinAddressRecord.fromJSON(unspent['address_record'] as String), BitcoinAddressRecord.fromJSON(unspent['address_record'].toString()),
data['unspent'] as Map<String, dynamic>)) unspent as Map<String, dynamic>))
.toList() .toList()
: null, : null,
); );
@ -212,7 +212,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['confirmations'] = confirmations; m['confirmations'] = confirmations;
m['fee'] = fee; m['fee'] = fee;
m['to'] = to; m['to'] = to;
m['unspent'] = unspents?.map((e) => e.toJson()) ?? []; m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? [];
return m; return m;
} }

View file

@ -183,7 +183,7 @@ abstract class ElectrumWalletBase
startRefresh, startRefresh,
ScanData( ScanData(
sendPort: receivePort.sendPort, sendPort: receivePort.sendPort,
primarySilentAddress: walletAddresses.primarySilentAddress!, silentAddress: walletAddresses.silentAddress!,
network: network, network: network,
height: height, height: height,
chainTip: currentChainTip, chainTip: currentChainTip,
@ -211,25 +211,36 @@ abstract class ElectrumWalletBase
final existingTxInfo = transactionHistory.transactions[txid]; final existingTxInfo = transactionHistory.transactions[txid];
if (existingTxInfo != null) { if (existingTxInfo != null) {
final newUnspents = tx.unspents! final newUnspents = tx.unspents!
.where((unspent) => !existingTxInfo.unspents!.any((element) => .where((unspent) => !(existingTxInfo.unspents?.any((element) =>
element.hash.contains(unspent.hash) && element.vout == unspent.vout)) element.hash.contains(unspent.hash) && element.vout == unspent.vout) ??
false))
.toList(); .toList();
if (newUnspents.isNotEmpty) { if (newUnspents.isNotEmpty) {
existingTxInfo.unspents ??= []; existingTxInfo.unspents ??= [];
existingTxInfo.unspents!.addAll(newUnspents); existingTxInfo.unspents!.addAll(newUnspents);
existingTxInfo.amount += newUnspents.length > 1
final amount = newUnspents.length > 1
? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent) ? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent)
: newUnspents[0].value; : newUnspents[0].value;
if (existingTxInfo.direction == TransactionDirection.incoming) {
existingTxInfo.amount += amount;
} else {
existingTxInfo.amount = amount;
existingTxInfo.direction = TransactionDirection.incoming;
}
transactionHistory.addOne(existingTxInfo);
} }
} else { } else {
transactionHistory.addMany(message); transactionHistory.addMany(message);
transactionHistory.save();
} }
await transactionHistory.save();
await updateUnspent();
await save();
} }
} }
updateUnspent();
} }
// check if is a SyncStatus type since "is SyncStatus" doesn't work here // check if is a SyncStatus type since "is SyncStatus" doesn't work here
@ -284,7 +295,6 @@ abstract class ElectrumWalletBase
electrumClient.onConnectionStatusChange = (bool isConnected) async { electrumClient.onConnectionStatusChange = (bool isConnected) async {
if (!isConnected) { if (!isConnected) {
syncStatus = LostConnectionSyncStatus(); syncStatus = LostConnectionSyncStatus();
await electrumClient.close();
if (attemptedReconnect == false) { if (attemptedReconnect == false) {
await _electrumConnect(node, attemptedReconnect: true); await _electrumConnect(node, attemptedReconnect: true);
} }
@ -330,10 +340,10 @@ abstract class ElectrumWalletBase
ECPrivate? privkey; ECPrivate? privkey;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
privkey = walletAddresses.primarySilentAddress!.b_spend.clone().tweakAdd( privkey = walletAddresses.silentAddress!.b_spend.clone().tweakAdd(
BigintUtils.fromBytes(BytesUtils.fromHexString( BigintUtils.fromBytes(BytesUtils.fromHexString(
(utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord) (utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord)
.silentPaymentTweak)), .silentPaymentTweak!)),
); );
} else { } else {
privkey = generateECPrivate( privkey = generateECPrivate(
@ -693,11 +703,7 @@ abstract class ElectrumWalletBase
// 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) {
if (!unspentCoins.any((utx) => updatedUnspentCoins.addAll(tx.unspents!);
tx.unspents!.any((element) => utx.hash.contains(element.hash)) &&
tx.unspents!.any((element) => utx.vout == element.vout))) {
updatedUnspentCoins.addAll(tx.unspents!);
}
} }
}); });
@ -799,8 +805,7 @@ abstract class ElectrumWalletBase
(await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body); (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body);
time = status["block_time"] as int?; time = status["block_time"] as int?;
final tip = await electrumClient.getCurrentBlockChainTip() ?? 0; confirmations = currentChainTip! - (status["block_height"] as int? ?? 0);
confirmations = tip - (status["block_height"] as int? ?? 0);
} else { } else {
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
@ -843,13 +848,13 @@ abstract class ElectrumWalletBase
try { try {
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {}; final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
final currentHeight = await electrumClient.getCurrentBlockChainTip() ?? 0; currentChainTip ??= await electrumClient.getCurrentBlockChainTip() ?? 0;
await Future.wait(ADDRESS_TYPES.map((type) { await Future.wait(ADDRESS_TYPES.map((type) {
final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
return Future.wait(addressesByType.map((addressRecord) async { return Future.wait(addressesByType.map((addressRecord) async {
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight); final history = await _fetchAddressHistory(addressRecord, addressesSet, currentChainTip!);
if (history.isNotEmpty) { if (history.isNotEmpty) {
addressRecord.txCount = history.length; addressRecord.txCount = history.length;
@ -866,7 +871,7 @@ abstract class ElectrumWalletBase
matchedAddresses.toList(), matchedAddresses.toList(),
addressRecord.isHidden, addressRecord.isHidden,
(address, addressesSet) => (address, addressesSet) =>
_fetchAddressHistory(address, addressesSet, currentHeight) _fetchAddressHistory(address, addressesSet, currentChainTip!)
.then((history) => history.isNotEmpty ? address.address : null), .then((history) => history.isNotEmpty ? address.address : null),
type: type); type: type);
} }
@ -1064,16 +1069,14 @@ abstract class ElectrumWalletBase
} }
Future<void> _setInitialHeight() async { Future<void> _setInitialHeight() async {
if (walletInfo.restoreHeight == 0) { currentChainTip = await electrumClient.getCurrentBlockChainTip();
currentChainTip = await electrumClient.getCurrentBlockChainTip(); if (currentChainTip != null) walletInfo.restoreHeight = currentChainTip!;
if (currentChainTip != null) walletInfo.restoreHeight = currentChainTip!;
}
} }
} }
class ScanData { class ScanData {
final SendPort sendPort; final SendPort sendPort;
final SilentPaymentOwner primarySilentAddress; final SilentPaymentOwner silentAddress;
final int height; final int height;
final String node; final String node;
final BasedUtxoNetwork network; final BasedUtxoNetwork network;
@ -1084,7 +1087,7 @@ class ScanData {
ScanData({ ScanData({
required this.sendPort, required this.sendPort,
required this.primarySilentAddress, required this.silentAddress,
required this.height, required this.height,
required this.node, required this.node,
required this.network, required this.network,
@ -1097,7 +1100,7 @@ class ScanData {
factory ScanData.fromHeight(ScanData scanData, int newHeight) { factory ScanData.fromHeight(ScanData scanData, int newHeight) {
return ScanData( return ScanData(
sendPort: scanData.sendPort, sendPort: scanData.sendPort,
primarySilentAddress: scanData.primarySilentAddress, silentAddress: scanData.silentAddress,
height: newHeight, height: newHeight,
node: scanData.node, node: scanData.node,
network: scanData.network, network: scanData.network,
@ -1119,7 +1122,7 @@ class SyncResponse {
Future<void> startRefresh(ScanData scanData) async { Future<void> startRefresh(ScanData scanData) async {
var cachedBlockchainHeight = scanData.chainTip; var cachedBlockchainHeight = scanData.chainTip;
Future<ElectrumClient> connect() async { Future<ElectrumClient> getElectrumConnection() async {
final electrumClient = scanData.electrumClient; final electrumClient = scanData.electrumClient;
if (!electrumClient.isConnected) { if (!electrumClient.isConnected) {
final node = scanData.node; final node = scanData.node;
@ -1130,7 +1133,7 @@ Future<void> startRefresh(ScanData scanData) async {
Future<int> getNodeHeightOrUpdate(int baseHeight) async { Future<int> getNodeHeightOrUpdate(int baseHeight) async {
if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) {
final electrumClient = await connect(); final electrumClient = await getElectrumConnection();
cachedBlockchainHeight = cachedBlockchainHeight =
await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight;
@ -1174,7 +1177,7 @@ Future<void> startRefresh(ScanData scanData) async {
} }
try { try {
final electrumClient = await connect(); final electrumClient = await getElectrumConnection();
List<dynamic>? tweaks; List<dynamic>? tweaks;
try { try {
@ -1205,9 +1208,13 @@ Future<void> startRefresh(ScanData scanData) async {
final spb = SilentPaymentBuilder(receiverTweak: tweak); final spb = SilentPaymentBuilder(receiverTweak: tweak);
final result = spb.scanOutputs( final result = spb.scanOutputs(
scanData.primarySilentAddress.b_scan, scanData.silentAddress.b_scan,
scanData.primarySilentAddress.B_spend, scanData.silentAddress.B_spend,
output_pubkeys.map((output) => output.toString()).toList(), output_pubkeys
.map((output) =>
BytesUtils.toHexString(BytesUtils.fromHexString(output.toString()).sublist(2))
.toString())
.toList(),
precomputedLabels: scanData.labels, precomputedLabels: scanData.labels,
); );
@ -1226,15 +1233,12 @@ Future<void> startRefresh(ScanData scanData) async {
BitcoinUnspent? info; BitcoinUnspent? info;
await Future.forEach<Map<String, dynamic>>(listUnspent, (unspent) async { await Future.forEach<Map<String, dynamic>>(listUnspent, (unspent) async {
try { try {
final addressRecord = BitcoinSilentPaymentAddressRecord( final addressRecord = BitcoinSilentPaymentAddressRecord(address,
address, index: 0,
index: 0, isHidden: true,
isHidden: true, isUsed: true,
isUsed: true, network: scanData.network,
network: scanData.network, silentPaymentTweak: t_k);
silentPaymentTweak: t_k,
type: SegwitAddresType.p2tr,
);
info = BitcoinUnspent.fromJSON(addressRecord, unspent); info = BitcoinUnspent.fromJSON(addressRecord, unspent);
} catch (_) {} } catch (_) {}
}); });
@ -1258,7 +1262,7 @@ Future<void> startRefresh(ScanData scanData) async {
isPending: false, isPending: false,
date: DateTime.now(), date: DateTime.now(),
confirmations: currentChainTip - syncHeight - 1, confirmations: currentChainTip - syncHeight - 1,
to: scanData.primarySilentAddress.toString(), to: scanData.silentAddress.toString(),
unspents: [tx], unspents: [tx],
); );
@ -1310,7 +1314,6 @@ Future<void> startRefresh(ScanData scanData) async {
}); });
} catch (_) {} } catch (_) {}
} }
// break;
// Finished scanning block, add 1 to height and continue to next block in loop // Finished scanning block, add 1 to height and continue to next block in loop
syncHeight += 1; syncHeight += 1;

View file

@ -30,10 +30,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0, int initialSilentAddressIndex = 1,
SilentPaymentOwner? silentAddress, bitcoin.HDWallet? masterHd,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()), }) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
primarySilentAddress = silentAddress,
addressesByReceiveType = addressesByReceiveType =
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()), ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []) receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
@ -51,6 +50,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
(initialSilentAddresses ?? []).toSet()), (initialSilentAddresses ?? []).toSet()),
currentSilentAddressIndex = initialSilentAddressIndex, currentSilentAddressIndex = initialSilentAddressIndex,
super(walletInfo) { super(walletInfo) {
if (masterHd != null) {
silentAddress = SilentPaymentOwner.fromPrivateKeys(
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!),
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
if (silentAddresses.length == 0)
silentAddresses.add(BitcoinSilentPaymentAddressRecord(silentAddress.toString(),
index: 1, isHidden: false, name: "", silentPaymentTweak: null, network: network));
}
updateAddressesByMatch(); updateAddressesByMatch();
} }
@ -70,7 +80,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final bitcoin.HDWallet mainHd; final bitcoin.HDWallet mainHd;
final bitcoin.HDWallet sideHd; final bitcoin.HDWallet sideHd;
final SilentPaymentOwner? primarySilentAddress; @observable
SilentPaymentOwner? silentAddress;
@observable @observable
BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh; BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh;
@ -92,7 +103,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return activeSilentAddress!; return activeSilentAddress!;
} }
return primarySilentAddress!.toString(); return silentAddress.toString();
} }
String receiveAddress; String receiveAddress;
@ -123,7 +134,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override @override
set address(String addr) { set address(String addr) {
if (addressPageType == SilentPaymentsAddresType.p2sp) { if (addressPageType == SilentPaymentsAddresType.p2sp) {
activeSilentAddress = addr; final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
if (selected.silentPaymentTweak != null && silentAddress != null) {
activeSilentAddress =
silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString();
} else {
activeSilentAddress = silentAddress!.toString();
}
return; return;
} }
@ -225,27 +243,28 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
for (int i = 0; i < silentAddresses.length; i++) { for (int i = 0; i < silentAddresses.length; i++) {
final silentAddressRecord = silentAddresses[i]; final silentAddressRecord = silentAddresses[i];
final silentPaymentTweak = silentAddressRecord.silentPaymentTweak; final silentPaymentTweak = silentAddressRecord.silentPaymentTweak;
labels[G
.tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak))) if (silentPaymentTweak != null)
.toHex()] = silentPaymentTweak; labels[G
.tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak)))
.toHex()] = silentPaymentTweak;
} }
return labels; return labels;
} }
@action @action
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
if (addressPageType == SilentPaymentsAddresType.p2sp) { if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) {
currentSilentAddressIndex += 1; currentSilentAddressIndex += 1;
final address = BitcoinSilentPaymentAddressRecord( final address = BitcoinSilentPaymentAddressRecord(
primarySilentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(), silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(),
index: currentSilentAddressIndex, index: currentSilentAddressIndex,
isHidden: false, isHidden: false,
name: label, name: label,
silentPaymentTweak: silentPaymentTweak:
BytesUtils.toHexString(primarySilentAddress!.generateLabel(currentSilentAddressIndex)), BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)),
network: network, network: network,
type: SilentPaymentsAddresType.p2sp,
); );
silentAddresses.add(address); silentAddresses.add(address);

View file

@ -78,11 +78,9 @@ packages:
bitcoin_base: bitcoin_base:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "/home/rafael/Working/bitcoin_base"
ref: cake-update-v2 relative: false
resolved-ref: "7634511b15e5a48bd18e3c19f81971628090c04f" source: path
url: "https://github.com/cake-tech/bitcoin_base.git"
source: git
version: "4.0.0" version: "4.0.0"
bitcoin_flutter: bitcoin_flutter:
dependency: "direct main" dependency: "direct main"
@ -96,11 +94,9 @@ packages:
blockchain_utils: blockchain_utils:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "/home/rafael/Working/blockchain_utils"
ref: cake-update-v1 relative: false
resolved-ref: "6a0b891db4d90c647ebf5fc3a9132e614c70e1c6" source: path
url: "https://github.com/cake-tech/blockchain_utils"
source: git
version: "1.6.0" version: "1.6.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive

View file

@ -31,13 +31,9 @@ dependencies:
unorm_dart: ^0.2.0 unorm_dart: ^0.2.0
cryptography: ^2.0.5 cryptography: ^2.0.5
bitcoin_base: bitcoin_base:
git: path: /home/rafael/Working/bitcoin_base
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v2
blockchain_utils: blockchain_utils:
git: path: /home/rafael/Working/blockchain_utils
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -30,13 +30,9 @@ dependencies:
url: https://github.com/cake-tech/bitbox-flutter.git url: https://github.com/cake-tech/bitbox-flutter.git
ref: master ref: master
bitcoin_base: bitcoin_base:
git: path: /home/rafael/Working/bitcoin_base
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v2
blockchain_utils: blockchain_utils:
git: path: /home/rafael/Working/blockchain_utils
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -0,0 +1,75 @@
#!/usr/bin/env fish
set APP_ANDROID_NAME ""
set APP_ANDROID_VERSION ""
set APP_ANDROID_BUILD_VERSION ""
set APP_ANDROID_ID ""
set APP_ANDROID_PACKAGE ""
set APP_ANDROID_SCHEME ""
set MONERO_COM "monero.com"
set CAKEWALLET "cakewallet"
set HAVEN "haven"
set -l TYPES $MONERO_COM $CAKEWALLET $HAVEN
set APP_ANDROID_TYPE $argv[1]
set MONERO_COM_NAME "Monero.com"
set MONERO_COM_VERSION "1.10.0"
set MONERO_COM_BUILD_NUMBER 72
set MONERO_COM_BUNDLE_ID "com.monero.app"
set MONERO_COM_PACKAGE "com.monero.app"
set MONERO_COM_SCHEME "monero.com"
set CAKEWALLET_NAME "Cake Wallet"
set CAKEWALLET_VERSION "4.13.0"
set CAKEWALLET_BUILD_NUMBER 189
set CAKEWALLET_BUNDLE_ID "com.cakewallet.cake_wallet"
set CAKEWALLET_PACKAGE "com.cakewallet.cake_wallet"
set CAKEWALLET_SCHEME "cakewallet"
set HAVEN_NAME "Haven"
set HAVEN_VERSION "1.0.0"
set HAVEN_BUILD_NUMBER 1
set HAVEN_BUNDLE_ID "com.cakewallet.haven"
set HAVEN_PACKAGE "com.cakewallet.haven"
if not contains $APP_ANDROID_TYPE $TYPES
echo "Wrong app type."
return 1
exit 1
end
switch $APP_ANDROID_TYPE
case $MONERO_COM
set APP_ANDROID_NAME $MONERO_COM_NAME
set APP_ANDROID_VERSION $MONERO_COM_VERSION
set APP_ANDROID_BUILD_NUMBER $MONERO_COM_BUILD_NUMBER
set APP_ANDROID_BUNDLE_ID $MONERO_COM_BUNDLE_ID
set APP_ANDROID_PACKAGE $MONERO_COM_PACKAGE
set APP_ANDROID_SCHEME $MONERO_COM_SCHEME
;;
case $CAKEWALLET
set APP_ANDROID_NAME $CAKEWALLET_NAME
set APP_ANDROID_VERSION $CAKEWALLET_VERSION
set APP_ANDROID_BUILD_NUMBER $CAKEWALLET_BUILD_NUMBER
set APP_ANDROID_BUNDLE_ID $CAKEWALLET_BUNDLE_ID
set APP_ANDROID_PACKAGE $CAKEWALLET_PACKAGE
set APP_ANDROID_SCHEME $CAKEWALLET_SCHEME
;;
case $HAVEN
set APP_ANDROID_NAME $HAVEN_NAME
set APP_ANDROID_VERSION $HAVEN_VERSION
set APP_ANDROID_BUILD_NUMBER $HAVEN_BUILD_NUMBER
set APP_ANDROID_BUNDLE_ID $HAVEN_BUNDLE_ID
set APP_ANDROID_PACKAGE $HAVEN_PACKAGE
;;
end
export APP_ANDROID_TYPE
export APP_ANDROID_NAME
export APP_ANDROID_VERSION
export APP_ANDROID_BUILD_NUMBER
export APP_ANDROID_BUNDLE_ID
export APP_ANDROID_PACKAGE
export APP_ANDROID_SCHEME