improve wallet descriptor and add output descriptor retrieval

This commit is contained in:
Craig Raw 2020-09-09 11:18:13 +02:00
parent e65f1ef3cc
commit b8c3bf1bea
9 changed files with 166 additions and 15 deletions

2
drongo

@ -1 +1 @@
Subproject commit 08c159ebadee78af391e83d91b6a453a28acbf3e Subproject commit 488752c142765bacd0373390faccbdb11b47487a

View file

@ -1,6 +1,9 @@
package com.sparrowwallet.sparrow; package com.sparrowwallet.sparrow;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptChunk; import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.sparrow.control.DescriptorArea;
import com.sparrowwallet.sparrow.control.ScriptArea; import com.sparrowwallet.sparrow.control.ScriptArea;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@ -39,4 +42,42 @@ public abstract class BaseController {
protected String describeScriptChunk(ScriptChunk chunk) { protected String describeScriptChunk(ScriptChunk chunk) {
return chunk.toString(); return chunk.toString();
} }
protected void initializeDescriptorField(DescriptorArea descriptorArea) {
Popup popup = new Popup();
Label popupMsg = new Label();
popupMsg.getStyleClass().add("tooltip");
popup.getContent().add(popupMsg);
descriptorArea.setMouseOverTextDelay(Duration.ofMillis(150));
descriptorArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> {
TwoDimensional.Position position = descriptorArea.getParagraph(0).getStyleSpans().offsetToPosition(e.getCharacterIndex(), Backward);
int index = descriptorArea.getWallet().getPolicyType() == PolicyType.SINGLE ? position.getMajor() - 1 : ((position.getMajor() - 1) / 2);
if(position.getMajor() > 0 && index >= 0 && index < descriptorArea.getWallet().getKeystores().size()) {
Keystore hoverKeystore = descriptorArea.getWallet().getKeystores().get(index);
Point2D pos = e.getScreenPosition();
popupMsg.setText(describeKeystore(hoverKeystore));
popup.show(descriptorArea, pos.getX(), pos.getY() + 10);
}
});
descriptorArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> {
popup.hide();
});
}
protected String describeKeystore(Keystore keystore) {
if(keystore.isValid()) {
StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append(keystore.getKeyDerivation().getMasterFingerprint());
builder.append("/");
builder.append(keystore.getKeyDerivation().getDerivationPath().replaceFirst("^m?/", ""));
builder.append("]");
builder.append(keystore.getExtendedPublicKey().toString());
return builder.toString();
}
return "Invalid";
}
} }

View file

@ -0,0 +1,89 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.OutputDescriptor;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import org.fxmisc.richtext.CodeArea;
import java.util.List;
import static com.sparrowwallet.drongo.policy.PolicyType.MULTI;
import static com.sparrowwallet.drongo.policy.PolicyType.SINGLE;
import static com.sparrowwallet.drongo.protocol.ScriptType.MULTISIG;
public class DescriptorArea extends CodeArea {
private Wallet wallet;
public void setWallet(Wallet wallet) {
clear();
this.wallet = wallet;
DescriptorContextMenu contextMenu = new DescriptorContextMenu(wallet, this);
setContextMenu(contextMenu);
PolicyType policyType = wallet.getPolicyType();
ScriptType scriptType = wallet.getScriptType();
List<Keystore> keystores = wallet.getKeystores();
int threshold = wallet.getDefaultPolicy().getNumSignaturesRequired();
if(SINGLE.equals(policyType)) {
append(scriptType.getDescriptor(), "descriptor-text");
replace(getLength(), getLength(), keystores.get(0).getScriptName(), List.of(keystores.get(0).isValid() ? "descriptor-text" : "descriptor-error", keystores.get(0).getScriptName()));
append(scriptType.getCloseDescriptor(), "descriptor-text");
}
if(MULTI.equals(policyType)) {
append(scriptType.getDescriptor(), "descriptor-text");
append(MULTISIG.getDescriptor(), "descriptor-text");
append(Integer.toString(threshold), "descriptor-text");
for(Keystore keystore : keystores) {
append(",", "descriptor-text");
replace(getLength(), getLength(), keystore.getScriptName(), List.of(keystore.isValid() ? "descriptor-text" : "descriptor-error", keystore.getScriptName()));
}
append(MULTISIG.getCloseDescriptor(), "descriptor-text");
append(scriptType.getCloseDescriptor(), "descriptor-text");
}
}
public Wallet getWallet() {
return wallet;
}
public void clear() {
super.clear();
this.wallet = null;
setDisable(false);
setContextMenu(null);
}
private static class DescriptorContextMenu extends ContextMenu {
public DescriptorContextMenu(Wallet wallet, DescriptorArea descriptorArea) {
MenuItem copyvalue = new MenuItem("Copy Value");
copyvalue.setOnAction(AE -> {
hide();
ClipboardContent content = new ClipboardContent();
content.putString(descriptorArea.getText());
Clipboard.getSystemClipboard().setContent(content);
});
getItems().add(copyvalue);
MenuItem copyOutputDescriptor = new MenuItem("Copy Output Descriptor");
copyOutputDescriptor.setOnAction(AE -> {
hide();
ClipboardContent content = new ClipboardContent();
content.putString(OutputDescriptor.getOutputDescriptor(wallet).toString(true));
Clipboard.getSystemClipboard().setContent(content);
});
getItems().add(copyOutputDescriptor);
this.setStyle("-fx-background-color: -fx-color; -fx-font-family: System; -fx-font-size: 1em;");
}
}
}

View file

@ -2,7 +2,6 @@ package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptChunk; import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.sparrow.transaction.ScriptContextMenu;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import org.controlsfx.control.decoration.Decorator; import org.controlsfx.control.decoration.Decorator;
import org.controlsfx.control.decoration.GraphicDecoration; import org.controlsfx.control.decoration.GraphicDecoration;

View file

@ -1,4 +1,4 @@
package com.sparrowwallet.sparrow.transaction; package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptChunk; import com.sparrowwallet.drongo.protocol.ScriptChunk;
@ -33,7 +33,7 @@ public class ScriptContextMenu extends ContextMenu {
}); });
getItems().add(copyvalue); getItems().add(copyvalue);
this.setStyle("-fx-background-color: -fx-color; -fx-font-family: sans-serif; -fx-font-size: 1em;"); this.setStyle("-fx-background-color: -fx-color; -fx-font-family: System; -fx-font-size: 1em;");
area.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> { area.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
hoverChunk = null; hoverChunk = null;

View file

@ -13,6 +13,7 @@ import com.sparrowwallet.drongo.wallet.WalletModel;
import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.control.DescriptorArea;
import com.sparrowwallet.sparrow.control.WalletPasswordDialog; import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
import com.sparrowwallet.sparrow.event.SettingsChangedEvent; import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
import com.sparrowwallet.sparrow.event.StorageEvent; import com.sparrowwallet.sparrow.event.StorageEvent;
@ -46,7 +47,7 @@ public class SettingsController extends WalletFormController implements Initiali
private ComboBox<PolicyType> policyType; private ComboBox<PolicyType> policyType;
@FXML @FXML
private TextField spendingMiniscript; private DescriptorArea descriptor;
@FXML @FXML
private ComboBox<ScriptType> scriptType; private ComboBox<ScriptType> scriptType;
@ -160,6 +161,8 @@ public class SettingsController extends WalletFormController implements Initiali
} }
}); });
initializeDescriptorField(descriptor);
revert.setOnAction(event -> { revert.setOnAction(event -> {
keystoreTabs.getTabs().removeAll(keystoreTabs.getTabs()); keystoreTabs.getTabs().removeAll(keystoreTabs.getTabs());
totalKeystores.unbind(); totalKeystores.unbind();
@ -224,6 +227,7 @@ public class SettingsController extends WalletFormController implements Initiali
KeystoreController controller = keystoreLoader.getController(); KeystoreController controller = keystoreLoader.getController();
controller.setKeystore(getWalletForm(), keystore); controller.setKeystore(getWalletForm(), keystore);
tab.textProperty().bind(controller.getLabel().textProperty()); tab.textProperty().bind(controller.getLabel().textProperty());
tab.setUserData(keystore);
controller.getValidationSupport().validationResultProperty().addListener((o, oldValue, result) -> { controller.getValidationSupport().validationResultProperty().addListener((o, oldValue, result) -> {
if(result.getErrors().isEmpty()) { if(result.getErrors().isEmpty()) {
@ -243,6 +247,19 @@ public class SettingsController extends WalletFormController implements Initiali
} }
} }
@Override
protected String describeKeystore(Keystore keystore) {
if(!keystore.isValid()) {
for(Tab tab : keystoreTabs.getTabs()) {
if(tab.getUserData() == keystore && tab.getTooltip() != null) {
return tab.getTooltip().getText();
}
}
}
return super.describeKeystore(keystore);
}
@Subscribe @Subscribe
public void update(SettingsChangedEvent event) { public void update(SettingsChangedEvent event) {
Wallet wallet = event.getWallet(); Wallet wallet = event.getWallet();
@ -253,7 +270,7 @@ public class SettingsController extends WalletFormController implements Initiali
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue())); wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue()));
} }
spendingMiniscript.setText(wallet.getDefaultPolicy().getMiniscript().getScript()); descriptor.setWallet(wallet);
revert.setDisable(false); revert.setDisable(false);
apply.setDisable(!wallet.isValid()); apply.setDisable(!wallet.isValid());
} }

View file

@ -0,0 +1,3 @@
.descriptor-text { -fx-fill: #000000 }
.descriptor-error { -fx-fill: #ca1243 }

View file

@ -10,8 +10,4 @@
-fx-alignment: center-left; -fx-alignment: center-left;
} }
#spendingMiniscript {
-fx-font-size: 13px;
-fx-font-family: 'Roboto Mono';
}

View file

@ -9,8 +9,10 @@
<?import com.sparrowwallet.sparrow.control.CopyableLabel?> <?import com.sparrowwallet.sparrow.control.CopyableLabel?>
<?import com.sparrowwallet.drongo.policy.PolicyType?> <?import com.sparrowwallet.drongo.policy.PolicyType?>
<?import com.sparrowwallet.drongo.protocol.ScriptType?> <?import com.sparrowwallet.drongo.protocol.ScriptType?>
<?import com.sparrowwallet.sparrow.control.DescriptorArea?>
<?import org.fxmisc.flowless.VirtualizedScrollPane?>
<BorderPane stylesheets="@settings.css, @wallet.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SettingsController"> <BorderPane stylesheets="@settings.css, @wallet.css, @../script.css, @../descriptor.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SettingsController">
<center> <center>
<GridPane hgap="10.0" vgap="10.0"> <GridPane hgap="10.0" vgap="10.0">
<padding> <padding>
@ -59,7 +61,7 @@
<Form GridPane.columnIndex="1" GridPane.rowIndex="0"> <Form GridPane.columnIndex="1" GridPane.rowIndex="0">
<Fieldset inputGrow="SOMETIMES" text="" fx:id="multisigFieldset"> <Fieldset inputGrow="SOMETIMES" text="" fx:id="multisigFieldset">
<Field text="Cosigners:"> <Field text="Cosigners:">
<RangeSlider fx:id="multisigControl" showTickMarks="true" showTickLabels="true" blockIncrement="1" min="2" max="9" lowValue="2" highValue="3" snapToTicks="true" majorTickUnit="1" minorTickCount="0" /> <RangeSlider fx:id="multisigControl" showTickMarks="true" showTickLabels="true" blockIncrement="1" min="1" max="9" highValue="3" lowValue="2" snapToTicks="true" majorTickUnit="1" minorTickCount="0" />
</Field> </Field>
<Field text="M of N:"> <Field text="M of N:">
<CopyableLabel fx:id="multisigLowLabel" /> <CopyableLabel fx:id="multisigLowLabel" />
@ -70,9 +72,13 @@
</Form> </Form>
<Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="1"> <Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="1">
<Fieldset inputGrow="SOMETIMES" text="Spending Policy"> <Fieldset inputGrow="SOMETIMES" text="Script Policy">
<Field text="Miniscript:"> <Field text="Descriptor:">
<TextField fx:id="spendingMiniscript" editable="false" /> <VirtualizedScrollPane hbarPolicy="NEVER" vbarPolicy="NEVER">
<content>
<DescriptorArea fx:id="descriptor" editable="false" styleClass="uneditable-codearea" prefHeight="27" maxHeight="27" />
</content>
</VirtualizedScrollPane>
</Field> </Field>
</Fieldset> </Fieldset>
</Form> </Form>