mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 13:16:44 +00:00
implement bip329 for importing and exporting wallet labels
This commit is contained in:
parent
8d584d1c48
commit
555260e954
12 changed files with 359 additions and 13 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit b487396417fbdf3c73c24399a778855c97a26584
|
Subproject commit d48054ac6ba071b49180017fe12e3acb41635b9c
|
|
@ -1068,13 +1068,16 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void importWallet(ActionEvent event) {
|
public void importWallet(ActionEvent event) {
|
||||||
WalletImportDialog dlg = new WalletImportDialog();
|
List<WalletForm> selectedWalletForms = getSelectedWalletForms();
|
||||||
|
WalletImportDialog dlg = new WalletImportDialog(selectedWalletForms);
|
||||||
Optional<Wallet> optionalWallet = dlg.showAndWait();
|
Optional<Wallet> optionalWallet = dlg.showAndWait();
|
||||||
if(optionalWallet.isPresent()) {
|
if(optionalWallet.isPresent()) {
|
||||||
Wallet wallet = optionalWallet.get();
|
Wallet wallet = optionalWallet.get();
|
||||||
|
if(selectedWalletForms.isEmpty() || wallet != selectedWalletForms.get(0).getWallet()) {
|
||||||
addImportedWallet(wallet);
|
addImportedWallet(wallet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean attemptImportWallet(File file, SecureString password) {
|
private boolean attemptImportWallet(File file, SecureString password) {
|
||||||
List<WalletImport> walletImporters = List.of(new ColdcardSinglesig(), new ColdcardMultisig(),
|
List<WalletImport> walletImporters = List.of(new ColdcardSinglesig(), new ColdcardMultisig(),
|
||||||
|
@ -1653,6 +1656,19 @@ public class AppController implements Initializable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<WalletForm> getSelectedWalletForms() {
|
||||||
|
Tab selectedTab = tabs.getSelectionModel().getSelectedItem();
|
||||||
|
if(selectedTab != null) {
|
||||||
|
TabData tabData = (TabData) selectedTab.getUserData();
|
||||||
|
if(tabData instanceof WalletTabData) {
|
||||||
|
TabPane subTabs = (TabPane) selectedTab.getContent();
|
||||||
|
return subTabs.getTabs().stream().map(subTab -> ((WalletTabData) subTab.getUserData()).getWalletForm()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
private void addTransactionTab(String name, File file, String string) throws ParseException, PSBTParseException, TransactionParseException {
|
private void addTransactionTab(String name, File file, String string) throws ParseException, PSBTParseException, TransactionParseException {
|
||||||
if(Utils.isBase64(string) && !Utils.isHex(string)) {
|
if(Utils.isBase64(string) && !Utils.isHex(string)) {
|
||||||
addTransactionTab(name, file, Base64.getDecoder().decode(string));
|
addTransactionTab(name, file, Base64.getDecoder().decode(string));
|
||||||
|
|
|
@ -975,7 +975,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
importButton.setVisible(true);
|
importButton.setVisible(true);
|
||||||
showHideLink.setText("Show derivation...");
|
showHideLink.setText("Show derivation...");
|
||||||
showHideLink.setVisible(true);
|
showHideLink.setVisible(!device.isCard());
|
||||||
List<ChildNumber> defaultDerivation = wallet.getScriptType() == null ? ScriptType.P2WPKH.getDefaultDerivation() : wallet.getScriptType().getDefaultDerivation();
|
List<ChildNumber> defaultDerivation = wallet.getScriptType() == null ? ScriptType.P2WPKH.getDefaultDerivation() : wallet.getScriptType().getDefaultDerivation();
|
||||||
setContent(getDerivationEntry(keyDerivation == null ? defaultDerivation : keyDerivation.getDerivation()));
|
setContent(getDerivationEntry(keyDerivation == null ? defaultDerivation : keyDerivation.getDerivation()));
|
||||||
} else if(deviceOperation.equals(DeviceOperation.SIGN)) {
|
} else if(deviceOperation.equals(DeviceOperation.SIGN)) {
|
||||||
|
|
|
@ -42,9 +42,9 @@ public class WalletExportDialog extends Dialog<Wallet> {
|
||||||
|
|
||||||
List<WalletExport> exporters;
|
List<WalletExport> exporters;
|
||||||
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
||||||
exporters = List.of(new Electrum(), new ElectrumPersonalServer(), new Descriptor(), new SpecterDesktop(), new Sparrow());
|
exporters = List.of(new Electrum(), new ElectrumPersonalServer(), new Descriptor(), new SpecterDesktop(), new Sparrow(), new WalletLabels());
|
||||||
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
||||||
exporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new ElectrumPersonalServer(), new KeystoneMultisig(), new Descriptor(), new JadeMultisig(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow());
|
exporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new ElectrumPersonalServer(), new KeystoneMultisig(), new Descriptor(), new JadeMultisig(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow(), new WalletLabels());
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType());
|
throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.sparrowwallet.sparrow.event.WalletImportEvent;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||||
import com.sparrowwallet.sparrow.io.*;
|
import com.sparrowwallet.sparrow.io.*;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
@ -16,6 +17,7 @@ import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.controlsfx.glyphfont.Glyph;
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -24,7 +26,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
||||||
private final Accordion importAccordion;
|
private final Accordion importAccordion;
|
||||||
private final Button scanButton;
|
private final Button scanButton;
|
||||||
|
|
||||||
public WalletImportDialog() {
|
public WalletImportDialog(List<WalletForm> selectedWalletForms) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
setOnCloseRequest(event -> {
|
setOnCloseRequest(event -> {
|
||||||
EventManager.get().unregister(this);
|
EventManager.get().unregister(this);
|
||||||
|
@ -57,7 +59,10 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<WalletImport> walletImporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow());
|
List<WalletImport> walletImporters = new ArrayList<>(List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow()));
|
||||||
|
if(!selectedWalletForms.isEmpty()) {
|
||||||
|
walletImporters.add(new WalletLabels(selectedWalletForms));
|
||||||
|
}
|
||||||
for(WalletImport importer : walletImporters) {
|
for(WalletImport importer : walletImporters) {
|
||||||
if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) {
|
if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) {
|
||||||
FileWalletImportPane importPane = new FileWalletImportPane(importer);
|
FileWalletImportPane importPane = new FileWalletImportPane(importer);
|
||||||
|
|
|
@ -11,22 +11,29 @@ import java.util.*;
|
||||||
public class WalletEntryLabelsChangedEvent extends WalletChangedEvent {
|
public class WalletEntryLabelsChangedEvent extends WalletChangedEvent {
|
||||||
//Contains the changed entry mapped to the entry that changed it, if changed recursively (otherwise null)
|
//Contains the changed entry mapped to the entry that changed it, if changed recursively (otherwise null)
|
||||||
private final Map<Entry, Entry> entrySourceMap;
|
private final Map<Entry, Entry> entrySourceMap;
|
||||||
|
private final boolean propagate;
|
||||||
|
|
||||||
public WalletEntryLabelsChangedEvent(Wallet wallet, Entry entry) {
|
public WalletEntryLabelsChangedEvent(Wallet wallet, Entry entry) {
|
||||||
this(wallet, List.of(entry));
|
this(wallet, List.of(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletEntryLabelsChangedEvent(Wallet wallet, List<Entry> entries) {
|
public WalletEntryLabelsChangedEvent(Wallet wallet, List<Entry> entries) {
|
||||||
|
this(wallet, entries, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalletEntryLabelsChangedEvent(Wallet wallet, List<Entry> entries, boolean propagate) {
|
||||||
super(wallet);
|
super(wallet);
|
||||||
this.entrySourceMap = new LinkedHashMap<>();
|
this.entrySourceMap = new LinkedHashMap<>();
|
||||||
for(Entry entry : entries) {
|
for(Entry entry : entries) {
|
||||||
entrySourceMap.put(entry, null);
|
entrySourceMap.put(entry, null);
|
||||||
}
|
}
|
||||||
|
this.propagate = propagate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletEntryLabelsChangedEvent(Wallet wallet, Map<Entry, Entry> entrySourceMap) {
|
public WalletEntryLabelsChangedEvent(Wallet wallet, Map<Entry, Entry> entrySourceMap) {
|
||||||
super(wallet);
|
super(wallet);
|
||||||
this.entrySourceMap = entrySourceMap;
|
this.entrySourceMap = entrySourceMap;
|
||||||
|
this.propagate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Entry> getEntries() {
|
public Collection<Entry> getEntries() {
|
||||||
|
@ -36,4 +43,8 @@ public class WalletEntryLabelsChangedEvent extends WalletChangedEvent {
|
||||||
public Entry getSource(Entry entry) {
|
public Entry getSource(Entry entry) {
|
||||||
return entrySourceMap.get(entry);
|
return entrySourceMap.get(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean propagate() {
|
||||||
|
return propagate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
296
src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java
Normal file
296
src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.KeystoreLabelsChangedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletEntryLabelsChangedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class WalletLabels implements WalletImport, WalletExport {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WalletLabels.class);
|
||||||
|
|
||||||
|
private final List<WalletForm> walletForms;
|
||||||
|
|
||||||
|
public WalletLabels() {
|
||||||
|
this.walletForms = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalletLabels(List<WalletForm> walletForms) {
|
||||||
|
this.walletForms = walletForms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEncrypted(File file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Wallet Labels";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WalletModel getWalletModel() {
|
||||||
|
return WalletModel.LABELS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||||
|
List<Label> labels = new ArrayList<>();
|
||||||
|
List<Wallet> allWallets = wallet.isMasterWallet() ? wallet.getAllWallets() : wallet.getMasterWallet().getAllWallets();
|
||||||
|
for(Wallet exportWallet : allWallets) {
|
||||||
|
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(exportWallet);
|
||||||
|
String origin = outputDescriptor.toString(true, false, false);
|
||||||
|
|
||||||
|
for(Keystore keystore : exportWallet.getKeystores()) {
|
||||||
|
if(keystore.getLabel() != null && !keystore.getLabel().isEmpty()) {
|
||||||
|
labels.add(new Label(Type.xpub, keystore.getExtendedPublicKey().toString(), keystore.getLabel(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(BlockTransaction blkTx : exportWallet.getWalletTransactions().values()) {
|
||||||
|
if(blkTx.getLabel() != null && !blkTx.getLabel().isEmpty()) {
|
||||||
|
labels.add(new Label(Type.tx, blkTx.getHashAsString(), blkTx.getLabel(), origin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(WalletNode addressNode : exportWallet.getWalletAddresses().values()) {
|
||||||
|
if(addressNode.getLabel() != null && !addressNode.getLabel().isEmpty()) {
|
||||||
|
labels.add(new Label(Type.addr, addressNode.getAddress().toString(), addressNode.getLabel(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(BlockTransactionHashIndex txo : exportWallet.getWalletTxos().keySet()) {
|
||||||
|
if(txo.getLabel() != null && !txo.getLabel().isEmpty()) {
|
||||||
|
labels.add(new Label(Type.output, txo.toString(), txo.getLabel(), null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(txo.isSpent() && txo.getSpentBy().getLabel() != null && !txo.getSpentBy().getLabel().isEmpty()) {
|
||||||
|
labels.add(new Label(Type.input, txo.getSpentBy().toString(), txo.getSpentBy().getLabel(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
for(Label label : labels) {
|
||||||
|
writer.write(gson.toJson(label) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.flush();
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.error("Error exporting labels", e);
|
||||||
|
throw new ExportException("Error exporting labels", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWalletExportDescription() {
|
||||||
|
return "Exports a file containing labels from this wallet in the BIP329 standard format.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getExportFileExtension(Wallet wallet) {
|
||||||
|
return "jsonl";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletExportScannable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean walletExportRequiresDecryption() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWalletImportDescription() {
|
||||||
|
return "Imports a file containing labels in the BIP329 standard format to the currently selected wallet.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
|
||||||
|
if(walletForms.isEmpty()) {
|
||||||
|
throw new IllegalStateException("No wallets to import labels for");
|
||||||
|
}
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
List<Label> labels = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||||
|
String line;
|
||||||
|
while((line = reader.readLine()) != null) {
|
||||||
|
Label label;
|
||||||
|
try {
|
||||||
|
label = gson.fromJson(line, Label.class);
|
||||||
|
} catch(Exception e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(label == null || label.type == null || label.ref == null || label.label == null || label.label.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
labels.add(label);
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
throw new ImportException("Error importing labels file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Wallet, List<Keystore>> changedWalletKeystores = new LinkedHashMap<>();
|
||||||
|
Map<Wallet, List<Entry>> changedWalletEntries = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for(WalletForm walletForm : walletForms) {
|
||||||
|
Wallet wallet = walletForm.getWallet();
|
||||||
|
if(!wallet.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(wallet);
|
||||||
|
String origin = outputDescriptor.toString(true, false, false);
|
||||||
|
|
||||||
|
List<Entry> transactionEntries = walletForm.getWalletTransactionsEntry().getChildren();
|
||||||
|
List<Entry> addressEntries = new ArrayList<>();
|
||||||
|
addressEntries.addAll(walletForm.getNodeEntry(KeyPurpose.RECEIVE).getChildren());
|
||||||
|
addressEntries.addAll(walletForm.getNodeEntry(KeyPurpose.CHANGE).getChildren());
|
||||||
|
List<Entry> utxoEntries = walletForm.getWalletUtxosEntry().getChildren();
|
||||||
|
|
||||||
|
for(Label label : labels) {
|
||||||
|
if(label.origin != null && !label.origin.equals(origin)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(label.type == Type.xpub) {
|
||||||
|
for(Keystore keystore : wallet.getKeystores()) {
|
||||||
|
if(keystore.getExtendedPublicKey().toString().equals(label.ref)) {
|
||||||
|
keystore.setLabel(label.label);
|
||||||
|
List<Keystore> changedKeystores = changedWalletKeystores.computeIfAbsent(wallet, w -> new ArrayList<>());
|
||||||
|
changedKeystores.add(keystore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(label.type == Type.tx) {
|
||||||
|
for(Entry entry : transactionEntries) {
|
||||||
|
if(entry instanceof TransactionEntry transactionEntry) {
|
||||||
|
BlockTransaction blkTx = transactionEntry.getBlockTransaction();
|
||||||
|
if(blkTx.getHashAsString().equals(label.ref)) {
|
||||||
|
transactionEntry.getBlockTransaction().setLabel(label.label);
|
||||||
|
transactionEntry.labelProperty().set(label.label);
|
||||||
|
addChangedEntry(changedWalletEntries, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(label.type == Type.addr) {
|
||||||
|
for(Entry addressEntry : addressEntries) {
|
||||||
|
if(addressEntry instanceof NodeEntry nodeEntry) {
|
||||||
|
WalletNode addressNode = nodeEntry.getNode();
|
||||||
|
if(addressNode.getAddress().toString().equals(label.ref)) {
|
||||||
|
nodeEntry.getNode().setLabel(label.label);
|
||||||
|
nodeEntry.labelProperty().set(label.label);
|
||||||
|
addChangedEntry(changedWalletEntries, addressEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(label.type == Type.output || label.type == Type.input) {
|
||||||
|
for(Entry entry : transactionEntries) {
|
||||||
|
for(Entry hashIndexEntry : entry.getChildren()) {
|
||||||
|
if(hashIndexEntry instanceof TransactionHashIndexEntry txioEntry) {
|
||||||
|
BlockTransactionHashIndex reference = txioEntry.getHashIndex();
|
||||||
|
if((label.type == Type.output && txioEntry.getType() == HashIndexEntry.Type.OUTPUT && reference.toString().equals(label.ref))
|
||||||
|
|| (label.type == Type.input && txioEntry.getType() == HashIndexEntry.Type.INPUT && reference.toString().equals(label.ref))) {
|
||||||
|
txioEntry.getHashIndex().setLabel(label.label);
|
||||||
|
txioEntry.labelProperty().set(label.label);
|
||||||
|
addChangedEntry(changedWalletEntries, txioEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Entry addressEntry : addressEntries) {
|
||||||
|
for(Entry entry : addressEntry.getChildren()) {
|
||||||
|
updateHashIndexEntryLabel(label, entry);
|
||||||
|
for(Entry spentEntry : entry.getChildren()) {
|
||||||
|
updateHashIndexEntryLabel(label, spentEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Entry entry : utxoEntries) {
|
||||||
|
updateHashIndexEntryLabel(label, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Map.Entry<Wallet, List<Keystore>> walletKeystores : changedWalletKeystores.entrySet()) {
|
||||||
|
Wallet wallet = walletKeystores.getKey();
|
||||||
|
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
||||||
|
EventManager.get().post(new KeystoreLabelsChangedEvent(wallet, wallet, storage.getWalletId(wallet), walletKeystores.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Map.Entry<Wallet, List<Entry>> walletEntries : changedWalletEntries.entrySet()) {
|
||||||
|
EventManager.get().post(new WalletEntryLabelsChangedEvent(walletEntries.getKey(), walletEntries.getValue(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return walletForms.get(0).getWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateHashIndexEntryLabel(Label label, Entry entry) {
|
||||||
|
if(entry instanceof HashIndexEntry hashIndexEntry) {
|
||||||
|
BlockTransactionHashIndex reference = hashIndexEntry.getHashIndex();
|
||||||
|
if((label.type == Type.output && hashIndexEntry.getType() == HashIndexEntry.Type.OUTPUT && reference.toString().equals(label.ref))
|
||||||
|
|| (label.type == Type.input && hashIndexEntry.getType() == HashIndexEntry.Type.INPUT && reference.toString().equals(label.ref))) {
|
||||||
|
hashIndexEntry.labelProperty().set(label.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addChangedEntry(Map<Wallet, List<Entry>> changedEntries, Entry entry) {
|
||||||
|
List<Entry> entries = changedEntries.computeIfAbsent(entry.getWallet(), wallet -> new ArrayList<>());
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletImportScannable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exportsAllWallets() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Type {
|
||||||
|
tx, addr, pubkey, input, output, xpub
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Label {
|
||||||
|
public Label(Type type, String ref, String label, String origin) {
|
||||||
|
this.type = type;
|
||||||
|
this.ref = ref;
|
||||||
|
this.label = label;
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
String ref;
|
||||||
|
String label;
|
||||||
|
String origin;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import com.sparrowwallet.sparrow.io.CardApi;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
|
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
@ -96,6 +97,11 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
private final ValidationSupport validationSupport = new ValidationSupport();
|
private final ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
|
||||||
|
private final ChangeListener<String> labelChangeListener = (observable, oldValue, newValue) -> {
|
||||||
|
keystore.setLabel(newValue);
|
||||||
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_LABEL));
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
|
@ -152,10 +158,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
keystore.setKeyDerivation(new KeyDerivation("",""));
|
keystore.setKeyDerivation(new KeyDerivation("",""));
|
||||||
}
|
}
|
||||||
|
|
||||||
label.textProperty().addListener((observable, oldValue, newValue) -> {
|
label.textProperty().addListener(labelChangeListener);
|
||||||
keystore.setLabel(newValue);
|
|
||||||
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_LABEL));
|
|
||||||
});
|
|
||||||
fingerprint.textProperty().addListener((observable, oldValue, newValue) -> {
|
fingerprint.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
keystore.setKeyDerivation(new KeyDerivation(newValue, keystore.getKeyDerivation().getDerivationPath()));
|
keystore.setKeyDerivation(new KeyDerivation(newValue, keystore.getKeyDerivation().getDerivationPath()));
|
||||||
fingerprintIcon.setHex(newValue.length() == 8 ? newValue : null);
|
fingerprintIcon.setHex(newValue.length() == 8 ? newValue : null);
|
||||||
|
@ -597,4 +600,18 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void keystoreLabelsChanged(KeystoreLabelsChangedEvent event) {
|
||||||
|
if(event.getWalletId().equals(walletForm.getWalletId())) {
|
||||||
|
for(Keystore changedKeystore : event.getChangedKeystores()) {
|
||||||
|
if(xpub.getText().trim().equals(changedKeystore.getExtendedPublicKey().toString()) && !label.getText().equals(changedKeystore.getLabel())) {
|
||||||
|
label.textProperty().removeListener(labelChangeListener);
|
||||||
|
label.setText(changedKeystore.getLabel());
|
||||||
|
keystore.setLabel(changedKeystore.getLabel());
|
||||||
|
label.textProperty().addListener(labelChangeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,7 +488,8 @@ public class WalletForm {
|
||||||
public void walletLabelsChanged(WalletEntryLabelsChangedEvent event) {
|
public void walletLabelsChanged(WalletEntryLabelsChangedEvent event) {
|
||||||
if(event.toThisOrNested(wallet)) {
|
if(event.toThisOrNested(wallet)) {
|
||||||
Map<Entry, Entry> labelChangedEntries = new LinkedHashMap<>();
|
Map<Entry, Entry> labelChangedEntries = new LinkedHashMap<>();
|
||||||
for(Entry entry : event.getEntries()) {
|
Collection<Entry> entries = event.propagate() ? event.getEntries() : Collections.emptyList();
|
||||||
|
for(Entry entry : entries) {
|
||||||
if(entry.getLabel() != null && !entry.getLabel().isEmpty()) {
|
if(entry.getLabel() != null && !entry.getLabel().isEmpty()) {
|
||||||
if(entry instanceof TransactionEntry transactionEntry) {
|
if(entry instanceof TransactionEntry transactionEntry) {
|
||||||
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
||||||
|
|
BIN
src/main/resources/image/labels.png
Normal file
BIN
src/main/resources/image/labels.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 979 B |
BIN
src/main/resources/image/labels@2x.png
Normal file
BIN
src/main/resources/image/labels@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
src/main/resources/image/labels@3x.png
Normal file
BIN
src/main/resources/image/labels@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Loading…
Reference in a new issue