feat: store labeled silent addresses and restore from snapshot

This commit is contained in:
Rafael Saes 2023-12-06 11:47:14 -03:00
parent 5939b310a0
commit b04b262761
5 changed files with 61 additions and 23 deletions

View file

@ -26,9 +26,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
bitcoin.NetworkType? networkType, bitcoin.NetworkType? networkType,
required Uint8List seedBytes, required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinAddressRecord>? initialSilentAddresses,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0, int initialChangeAddressIndex = 0,
int initialSilentAddressIndex = 0,
bitcoin.SilentPaymentReceiver? silentAddress}) bitcoin.SilentPaymentReceiver? silentAddress})
: super( : super(
networkType: networkType ?? bitcoin.bitcoin, networkType: networkType ?? bitcoin.bitcoin,
@ -41,24 +43,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletAddresses = BitcoinWalletAddresses(walletInfo, walletAddresses = BitcoinWalletAddresses(walletInfo,
transactionHistory: super.transactionHistory, transactionHistory: super.transactionHistory,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
initialSilentAddressIndex: initialSilentAddressIndex,
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"),
networkType: networkType ?? bitcoin.bitcoin, networkType: networkType ?? bitcoin.bitcoin,
silentAddress: silentAddress); silentAddress: silentAddress);
} }
static Future<BitcoinWallet> create( static Future<BitcoinWallet> create({
{required String mnemonic, required String mnemonic,
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
bitcoin.NetworkType? networkType, bitcoin.NetworkType? networkType,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinAddressRecord>? initialSilentAddresses,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0}) async { int initialChangeAddressIndex = 0,
int initialSilentAddressIndex = 0,
}) async {
return BitcoinWallet( return BitcoinWallet(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
@ -66,10 +73,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
networkType: networkType, networkType: networkType,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic), seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
initialSilentAddressIndex: initialSilentAddressIndex,
silentAddress: await bitcoin.SilentPaymentReceiver.fromMnemonic(mnemonic, silentAddress: await bitcoin.SilentPaymentReceiver.fromMnemonic(mnemonic,
hrp: networkType == bitcoin.bitcoin ? 'sp' : 'tsp')); hrp: networkType == bitcoin.bitcoin ? 'sp' : 'tsp'));
} }
@ -88,10 +97,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
networkType: snp.networkType, networkType: snp.networkType,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialSilentAddresses: snp.silentAddresses,
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic), seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex,
initialSilentAddressIndex: snp.silentAddressIndex,
silentAddress: await bitcoin.SilentPaymentReceiver.fromMnemonic(snp.mnemonic, silentAddress: await bitcoin.SilentPaymentReceiver.fromMnemonic(snp.mnemonic,
hrp: snp.networkType == bitcoin.bitcoin ? 'sp' : 'tsp')); hrp: snp.networkType == bitcoin.bitcoin ? 'sp' : 'tsp'));
} }

View file

@ -16,8 +16,10 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required super.networkType, required super.networkType,
required super.transactionHistory, required super.transactionHistory,
super.initialAddresses, super.initialAddresses,
super.initialSilentAddresses,
super.initialRegularAddressIndex = 0, super.initialRegularAddressIndex = 0,
super.initialChangeAddressIndex = 0, super.initialChangeAddressIndex = 0,
super.initialSilentAddressIndex = 0,
super.silentAddress, super.silentAddress,
}) : super(walletInfo); }) : super(walletInfo);

View file

@ -533,7 +533,9 @@ abstract class ElectrumWalletBase
'mnemonic': mnemonic, 'mnemonic': mnemonic,
'account_index': walletAddresses.currentReceiveAddressIndex.toString(), 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(),
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(),
'balance': balance[currency]?.toJSON(), 'balance': balance[currency]?.toJSON(),
'network_type': networkType == bitcoin.bitcoin ? 'mainnet' : 'testnet', 'network_type': networkType == bitcoin.bitcoin ? 'mainnet' : 'testnet',
}); });

View file

@ -19,8 +19,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required this.transactionHistory, required this.transactionHistory,
required this.networkType, required this.networkType,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinAddressRecord>? initialSilentAddresses,
int initialRegularAddressIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0, int initialChangeAddressIndex = 0,
int initialSilentAddressIndex = 0,
bitcoin.SilentPaymentReceiver? silentAddress, bitcoin.SilentPaymentReceiver? silentAddress,
}) : addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()), }) : addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
primarySilentAddress = silentAddress, primarySilentAddress = silentAddress,
@ -30,11 +32,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []) changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed) .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
.toSet()), .toSet()),
silentAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []) silentAddresses = ObservableList<BitcoinAddressRecord>.of((initialSilentAddresses ?? [])
.where((addressRecord) => addressRecord.silentAddressLabel != null) .where((addressRecord) =>
addressRecord.silentAddressLabel != null &&
addressRecord.silentPaymentTweak != null)
.toSet()), .toSet()),
currentReceiveAddressIndex = initialRegularAddressIndex, currentReceiveAddressIndex = initialRegularAddressIndex,
currentChangeAddressIndex = initialChangeAddressIndex, currentChangeAddressIndex = initialChangeAddressIndex,
currentSilentAddressIndex = initialSilentAddressIndex,
super(walletInfo); super(walletInfo);
static const defaultReceiveAddressesCount = 22; static const defaultReceiveAddressesCount = 22;
@ -106,7 +111,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
int currentReceiveAddressIndex; int currentReceiveAddressIndex;
int currentChangeAddressIndex; int currentChangeAddressIndex;
int currentSilentAddressIndex = 0; int currentSilentAddressIndex;
@computed @computed
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) { int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {

View file

@ -13,10 +13,12 @@ class ElectrumWalletSnapshot {
required this.password, required this.password,
required this.mnemonic, required this.mnemonic,
required this.addresses, required this.addresses,
required this.silentAddresses,
required this.balance, required this.balance,
required this.networkType, required this.networkType,
required this.regularAddressIndex, required this.regularAddressIndex,
required this.changeAddressIndex, required this.changeAddressIndex,
required this.silentAddressIndex,
}); });
final String name; final String name;
@ -25,30 +27,43 @@ class ElectrumWalletSnapshot {
String mnemonic; String mnemonic;
List<BitcoinAddressRecord> addresses; List<BitcoinAddressRecord> addresses;
List<BitcoinAddressRecord> silentAddresses;
ElectrumBalance balance; ElectrumBalance balance;
bitcoin.NetworkType networkType; bitcoin.NetworkType networkType;
int regularAddressIndex; int regularAddressIndex;
int changeAddressIndex; int changeAddressIndex;
int silentAddressIndex;
static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password) async { static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password) async {
final path = await pathForWallet(name: name, type: type); final path = await pathForWallet(name: name, type: type);
final jsonSource = await read(path: path, password: password); final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
final addressesTmp = data['addresses'] as List? ?? <Object>[];
final mnemonic = data['mnemonic'] as String; final mnemonic = data['mnemonic'] as String;
final addressesTmp = data['addresses'] as List? ?? <Object>[];
final addresses = addressesTmp final addresses = addressesTmp
.whereType<String>() .whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr)) .map((addr) => BitcoinAddressRecord.fromJSON(addr))
.toList(); .toList();
final silentAddressesTmp = data['silent_addresses'] as List? ?? <Object>[];
final silentAddresses = silentAddressesTmp
.whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
.toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
final networkType = data['network_type'] == 'testnet' ? bitcoin.testnet : bitcoin.bitcoin; final networkType = data['network_type'] == 'testnet' ? bitcoin.testnet : bitcoin.bitcoin;
var regularAddressIndex = 0; var regularAddressIndex = 0;
var changeAddressIndex = 0; var changeAddressIndex = 0;
var silentAddressIndex = 0;
try { try {
regularAddressIndex = int.parse(data['account_index'] as String? ?? '0'); regularAddressIndex = int.parse(data['account_index'] as String? ?? '0');
changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0'); changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0');
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
} catch (_) {} } catch (_) {}
return ElectrumWalletSnapshot( return ElectrumWalletSnapshot(
@ -57,9 +72,12 @@ class ElectrumWalletSnapshot {
password: password, password: password,
mnemonic: mnemonic, mnemonic: mnemonic,
addresses: addresses, addresses: addresses,
silentAddresses: silentAddresses,
balance: balance, balance: balance,
networkType: networkType, networkType: networkType,
regularAddressIndex: regularAddressIndex, regularAddressIndex: regularAddressIndex,
changeAddressIndex: changeAddressIndex); changeAddressIndex: changeAddressIndex,
silentAddressIndex: silentAddressIndex,
);
} }
} }