Craig Raw
3 years ago
13 changed files with 794 additions and 417 deletions
@ -0,0 +1,72 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.sparrowwallet.drongo.wallet.DeterministicSeed; |
|||
import com.sparrowwallet.drongo.wallet.Keystore; |
|||
import com.sparrowwallet.drongo.wallet.WalletModel; |
|||
import javafx.beans.property.SimpleListProperty; |
|||
import javafx.collections.FXCollections; |
|||
import javafx.collections.ObservableList; |
|||
import javafx.geometry.Orientation; |
|||
import javafx.scene.Node; |
|||
import javafx.scene.layout.StackPane; |
|||
import javafx.scene.layout.TilePane; |
|||
import javafx.scene.layout.VBox; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class MnemonicKeystoreDisplayPane extends MnemonicKeystorePane { |
|||
public MnemonicKeystoreDisplayPane(Keystore keystore) { |
|||
super(keystore.getSeed().getType().getName(), keystore.getSeed().needsPassphrase() ? "Passphrase entered" : "No passphrase", "", "image/" + WalletModel.SEED.getType() + ".png"); |
|||
showHideLink.setVisible(false); |
|||
buttonBox.getChildren().clear(); |
|||
|
|||
showWordList(keystore.getSeed()); |
|||
} |
|||
|
|||
private void showWordList(DeterministicSeed seed) { |
|||
List<String> words = seed.getMnemonicCode(); |
|||
setContent(getMnemonicWordsEntry(words.size())); |
|||
setExpanded(true); |
|||
|
|||
for(int i = 0; i < wordsPane.getChildren().size(); i++) { |
|||
WordEntry wordEntry = (WordEntry)wordsPane.getChildren().get(i); |
|||
wordEntry.getEditor().setText(words.get(i)); |
|||
wordEntry.getEditor().setEditable(false); |
|||
} |
|||
} |
|||
|
|||
protected Node getMnemonicWordsEntry(int numWords) { |
|||
VBox vBox = new VBox(); |
|||
vBox.setSpacing(10); |
|||
|
|||
wordsPane = new TilePane(); |
|||
wordsPane.setPrefRows(numWords / 3); |
|||
wordsPane.setHgap(10); |
|||
wordsPane.setVgap(10); |
|||
wordsPane.setOrientation(Orientation.VERTICAL); |
|||
|
|||
List<String> words = new ArrayList<>(); |
|||
for(int i = 0; i < numWords; i++) { |
|||
words.add(""); |
|||
} |
|||
|
|||
ObservableList<String> wordEntryList = FXCollections.observableArrayList(words); |
|||
wordEntriesProperty = new SimpleListProperty<>(wordEntryList); |
|||
List<WordEntry> wordEntries = new ArrayList<>(numWords); |
|||
for(int i = 0; i < numWords; i++) { |
|||
wordEntries.add(new WordEntry(i, wordEntryList)); |
|||
} |
|||
for(int i = 0; i < numWords - 1; i++) { |
|||
wordEntries.get(i).setNextEntry(wordEntries.get(i + 1)); |
|||
wordEntries.get(i).setNextField(wordEntries.get(i + 1).getEditor()); |
|||
} |
|||
wordsPane.getChildren().addAll(wordEntries); |
|||
|
|||
vBox.getChildren().add(wordsPane); |
|||
|
|||
StackPane stackPane = new StackPane(); |
|||
stackPane.getChildren().add(vBox); |
|||
return stackPane; |
|||
} |
|||
} |
@ -0,0 +1,344 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.sparrowwallet.drongo.wallet.Bip39MnemonicCode; |
|||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; |
|||
import javafx.application.Platform; |
|||
import javafx.beans.property.IntegerProperty; |
|||
import javafx.beans.property.SimpleIntegerProperty; |
|||
import javafx.beans.property.SimpleListProperty; |
|||
import javafx.beans.property.SimpleStringProperty; |
|||
import javafx.collections.FXCollections; |
|||
import javafx.collections.ListChangeListener; |
|||
import javafx.collections.ObservableList; |
|||
import javafx.geometry.Insets; |
|||
import javafx.geometry.Orientation; |
|||
import javafx.geometry.Pos; |
|||
import javafx.scene.Node; |
|||
import javafx.scene.control.*; |
|||
import javafx.scene.input.Clipboard; |
|||
import javafx.scene.layout.*; |
|||
import javafx.util.Callback; |
|||
import org.controlsfx.control.textfield.AutoCompletionBinding; |
|||
import org.controlsfx.control.textfield.CustomTextField; |
|||
import org.controlsfx.control.textfield.TextFields; |
|||
import org.controlsfx.glyphfont.Glyph; |
|||
import org.controlsfx.validation.ValidationResult; |
|||
import org.controlsfx.validation.ValidationSupport; |
|||
import org.controlsfx.validation.Validator; |
|||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
public class MnemonicKeystorePane extends TitledDescriptionPane { |
|||
protected SplitMenuButton enterMnemonicButton; |
|||
protected TilePane wordsPane; |
|||
protected Label validLabel; |
|||
protected Label invalidLabel; |
|||
|
|||
protected SimpleListProperty<String> wordEntriesProperty; |
|||
protected final SimpleStringProperty passphraseProperty = new SimpleStringProperty(); |
|||
protected IntegerProperty defaultWordSizeProperty; |
|||
|
|||
public MnemonicKeystorePane(String title, String description, String content, String imageUrl) { |
|||
super(title, description, content, imageUrl); |
|||
} |
|||
|
|||
@Override |
|||
protected Control createButton() { |
|||
createEnterMnemonicButton(); |
|||
return enterMnemonicButton; |
|||
} |
|||
|
|||
private void createEnterMnemonicButton() { |
|||
enterMnemonicButton = new SplitMenuButton(); |
|||
enterMnemonicButton.setAlignment(Pos.CENTER_RIGHT); |
|||
enterMnemonicButton.setText("Enter 24 Words"); |
|||
defaultWordSizeProperty = new SimpleIntegerProperty(24); |
|||
defaultWordSizeProperty.addListener((observable, oldValue, newValue) -> { |
|||
enterMnemonicButton.setText("Enter " + newValue + " Words"); |
|||
}); |
|||
enterMnemonicButton.setOnAction(event -> { |
|||
enterMnemonic(defaultWordSizeProperty.get()); |
|||
}); |
|||
int[] numberWords = new int[] {24, 21, 18, 15, 12}; |
|||
for(int i = 0; i < numberWords.length; i++) { |
|||
MenuItem item = new MenuItem("Enter " + numberWords[i] + " Words"); |
|||
final int words = numberWords[i]; |
|||
item.setOnAction(event -> { |
|||
defaultWordSizeProperty.set(words); |
|||
enterMnemonic(words); |
|||
}); |
|||
enterMnemonicButton.getItems().add(item); |
|||
} |
|||
enterMnemonicButton.managedProperty().bind(enterMnemonicButton.visibleProperty()); |
|||
} |
|||
|
|||
protected void enterMnemonic(int numWords) { |
|||
setDescription("Enter mnemonic word list"); |
|||
showHideLink.setVisible(false); |
|||
setContent(getMnemonicWordsEntry(numWords)); |
|||
setExpanded(true); |
|||
} |
|||
|
|||
protected Node getMnemonicWordsEntry(int numWords) { |
|||
VBox vBox = new VBox(); |
|||
vBox.setSpacing(10); |
|||
|
|||
wordsPane = new TilePane(); |
|||
wordsPane.setPrefRows(numWords/3); |
|||
wordsPane.setHgap(10); |
|||
wordsPane.setVgap(10); |
|||
wordsPane.setOrientation(Orientation.VERTICAL); |
|||
|
|||
List<String> words = new ArrayList<>(); |
|||
for(int i = 0; i < numWords; i++) { |
|||
words.add(""); |
|||
} |
|||
|
|||
ObservableList<String> wordEntryList = FXCollections.observableArrayList(words); |
|||
wordEntriesProperty = new SimpleListProperty<>(wordEntryList); |
|||
List<WordEntry> wordEntries = new ArrayList<>(numWords); |
|||
for(int i = 0; i < numWords; i++) { |
|||
wordEntries.add(new WordEntry(i, wordEntryList)); |
|||
} |
|||
for(int i = 0; i < numWords - 1; i++) { |
|||
wordEntries.get(i).setNextEntry(wordEntries.get(i + 1)); |
|||
wordEntries.get(i).setNextField(wordEntries.get(i + 1).getEditor()); |
|||
} |
|||
wordsPane.getChildren().addAll(wordEntries); |
|||
|
|||
vBox.getChildren().add(wordsPane); |
|||
|
|||
PassphraseEntry passphraseEntry = new PassphraseEntry(); |
|||
wordEntries.get(wordEntries.size() - 1).setNextField(passphraseEntry.getEditor()); |
|||
passphraseEntry.setPadding(new Insets(0, 26, 10, 10)); |
|||
vBox.getChildren().add(passphraseEntry); |
|||
|
|||
AnchorPane buttonPane = new AnchorPane(); |
|||
buttonPane.setPadding(new Insets(0, 26, 0, 10)); |
|||
|
|||
HBox leftBox = new HBox(10); |
|||
leftBox.getChildren().addAll(createLeftButtons()); |
|||
|
|||
buttonPane.getChildren().add(leftBox); |
|||
AnchorPane.setLeftAnchor(leftBox, 0.0); |
|||
|
|||
validLabel = new Label("Valid checksum", getValidGlyph()); |
|||
validLabel.setContentDisplay(ContentDisplay.LEFT); |
|||
validLabel.setGraphicTextGap(5.0); |
|||
validLabel.managedProperty().bind(validLabel.visibleProperty()); |
|||
validLabel.setVisible(false); |
|||
buttonPane.getChildren().add(validLabel); |
|||
AnchorPane.setTopAnchor(validLabel, 5.0); |
|||
AnchorPane.setLeftAnchor(validLabel, 0.0); |
|||
|
|||
invalidLabel = new Label("Invalid checksum", getInvalidGlyph()); |
|||
invalidLabel.setContentDisplay(ContentDisplay.LEFT); |
|||
invalidLabel.setGraphicTextGap(5.0); |
|||
invalidLabel.managedProperty().bind(invalidLabel.visibleProperty()); |
|||
invalidLabel.setVisible(false); |
|||
buttonPane.getChildren().add(invalidLabel); |
|||
AnchorPane.setTopAnchor(invalidLabel, 5.0); |
|||
AnchorPane.setLeftAnchor(invalidLabel, 0.0); |
|||
|
|||
wordEntriesProperty.addListener((ListChangeListener<String>) c -> { |
|||
boolean empty = true; |
|||
boolean validWords = true; |
|||
boolean validChecksum = false; |
|||
for(String word : wordEntryList) { |
|||
if(!word.isEmpty()) { |
|||
empty = false; |
|||
} |
|||
|
|||
if(!WordEntry.isValid(word)) { |
|||
validWords = false; |
|||
} |
|||
} |
|||
|
|||
onWordChange(empty, validWords, validChecksum); |
|||
}); |
|||
|
|||
HBox rightBox = new HBox(); |
|||
rightBox.setSpacing(10); |
|||
rightBox.getChildren().addAll(createRightButtons()); |
|||
|
|||
buttonPane.getChildren().add(rightBox); |
|||
AnchorPane.setRightAnchor(rightBox, 0.0); |
|||
|
|||
vBox.getChildren().add(buttonPane); |
|||
|
|||
Platform.runLater(() -> wordEntries.get(0).getEditor().requestFocus()); |
|||
|
|||
StackPane stackPane = new StackPane(); |
|||
stackPane.getChildren().add(vBox); |
|||
return stackPane; |
|||
} |
|||
|
|||
protected List<Node> createLeftButtons() { |
|||
return Collections.emptyList(); |
|||
} |
|||
|
|||
protected List<Node> createRightButtons() { |
|||
return Collections.emptyList(); |
|||
} |
|||
|
|||
protected void onWordChange(boolean empty, boolean validWords, boolean validChecksum) { |
|||
//nothing by default
|
|||
} |
|||
|
|||
protected static class WordEntry extends HBox { |
|||
private static List<String> wordList; |
|||
private final TextField wordField; |
|||
private WordEntry nextEntry; |
|||
private TextField nextField; |
|||
|
|||
public WordEntry(int wordNumber, ObservableList<String> wordEntryList) { |
|||
super(); |
|||
setAlignment(Pos.CENTER_RIGHT); |
|||
|
|||
setSpacing(10); |
|||
Label label = new Label((wordNumber+1) + "."); |
|||
label.setPrefWidth(22); |
|||
label.setAlignment(Pos.CENTER_RIGHT); |
|||
wordField = new TextField() { |
|||
@Override |
|||
public void paste() { |
|||
Clipboard clipboard = Clipboard.getSystemClipboard(); |
|||
if(clipboard.hasString() && clipboard.getString().matches("(?m).+[\\n\\s][\\S\\s]*")) { |
|||
String[] words = clipboard.getString().split("[\\n\\s]"); |
|||
WordEntry entry = WordEntry.this; |
|||
for(String word : words) { |
|||
if(entry.nextField != null) { |
|||
entry.nextField.requestFocus(); |
|||
} |
|||
|
|||
entry.wordField.setText(word); |
|||
entry = entry.nextEntry; |
|||
if(entry == null) { |
|||
break; |
|||
} |
|||
} |
|||
} else { |
|||
super.paste(); |
|||
} |
|||
} |
|||
}; |
|||
wordField.setMaxWidth(100); |
|||
TextFormatter<?> formatter = new TextFormatter<>((TextFormatter.Change change) -> { |
|||
String text = change.getText(); |
|||
// if text was added, fix the text to fit the requirements
|
|||
if(!text.isEmpty()) { |
|||
String newText = text.replace(" ", "").toLowerCase(); |
|||
int carretPos = change.getCaretPosition() - text.length() + newText.length(); |
|||
change.setText(newText); |
|||
// fix caret position based on difference in originally added text and fixed text
|
|||
change.selectRange(carretPos, carretPos); |
|||
} |
|||
return change; |
|||
}); |
|||
wordField.setTextFormatter(formatter); |
|||
|
|||
wordList = Bip39MnemonicCode.INSTANCE.getWordList(); |
|||
AutoCompletionBinding<String> autoCompletionBinding = TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList)); |
|||
autoCompletionBinding.setDelay(50); |
|||
autoCompletionBinding.setOnAutoCompleted(event -> { |
|||
if(nextField != null) { |
|||
nextField.requestFocus(); |
|||
} |
|||
}); |
|||
|
|||
ValidationSupport validationSupport = new ValidationSupport(); |
|||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); |
|||
validationSupport.registerValidator(wordField, Validator.combine( |
|||
Validator.createEmptyValidator("Word is required"), |
|||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid word", !wordList.contains(newValue)) |
|||
)); |
|||
|
|||
wordField.textProperty().addListener((observable, oldValue, newValue) -> { |
|||
wordEntryList.set(wordNumber, newValue); |
|||
}); |
|||
|
|||
this.getChildren().addAll(label, wordField); |
|||
} |
|||
|
|||
public TextField getEditor() { |
|||
return wordField; |
|||
} |
|||
|
|||
public void setNextEntry(WordEntry nextEntry) { |
|||
this.nextEntry = nextEntry; |
|||
} |
|||
|
|||
public void setNextField(TextField field) { |
|||
this.nextField = field; |
|||
} |
|||
|
|||
public static boolean isValid(String word) { |
|||
return wordList.contains(word); |
|||
} |
|||
} |
|||
|
|||
protected static class WordlistSuggestionProvider implements Callback<AutoCompletionBinding.ISuggestionRequest, Collection<String>> { |
|||
private final List<String> wordList; |
|||
|
|||
public WordlistSuggestionProvider(List<String> wordList) { |
|||
this.wordList = wordList; |
|||
} |
|||
|
|||
@Override |
|||
public Collection<String> call(AutoCompletionBinding.ISuggestionRequest request) { |
|||
List<String> suggestions = new ArrayList<>(); |
|||
if(!request.getUserText().isEmpty()) { |
|||
for(String word : wordList) { |
|||
if(word.startsWith(request.getUserText())) { |
|||
suggestions.add(word); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return suggestions; |
|||
} |
|||
} |
|||
|
|||
protected class PassphraseEntry extends HBox { |
|||
private final CustomTextField passphraseField; |
|||
|
|||
public PassphraseEntry() { |
|||
super(); |
|||
|
|||
setAlignment(Pos.CENTER_LEFT); |
|||
setSpacing(10); |
|||
Label passphraseLabel = new Label("Passphrase:"); |
|||
passphraseField = (CustomTextField) TextFields.createClearableTextField(); |
|||
passphraseProperty.bind(passphraseField.textProperty()); |
|||
passphraseField.setPromptText("Leave blank for none"); |
|||
|
|||
HelpLabel helpLabel = new HelpLabel(); |
|||
helpLabel.setStyle("-fx-padding: 0 0 0 0"); |
|||
helpLabel.setHelpText("A passphrase provides optional added security - it is not stored so it must be remembered!"); |
|||
|
|||
getChildren().addAll(passphraseLabel, passphraseField, helpLabel); |
|||
} |
|||
|
|||
public TextField getEditor() { |
|||
return passphraseField; |
|||
} |
|||
} |
|||
|
|||
public static Glyph getValidGlyph() { |
|||
Glyph validGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CHECK_CIRCLE); |
|||
validGlyph.getStyleClass().add("success"); |
|||
validGlyph.setFontSize(12); |
|||
return validGlyph; |
|||
} |
|||
|
|||
public static Glyph getInvalidGlyph() { |
|||
Glyph invalidGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE); |
|||
invalidGlyph.getStyleClass().add("failure"); |
|||
invalidGlyph.setFontSize(12); |
|||
return invalidGlyph; |
|||
} |
|||
} |
@ -0,0 +1,214 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.sparrowwallet.drongo.crypto.ChildNumber; |
|||
import com.sparrowwallet.drongo.policy.Policy; |
|||
import com.sparrowwallet.drongo.policy.PolicyType; |
|||
import com.sparrowwallet.drongo.protocol.ScriptType; |
|||
import com.sparrowwallet.drongo.wallet.Keystore; |
|||
import com.sparrowwallet.drongo.wallet.MnemonicException; |
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
import com.sparrowwallet.sparrow.AppServices; |
|||
import com.sparrowwallet.sparrow.EventManager; |
|||
import com.sparrowwallet.sparrow.event.WalletImportEvent; |
|||
import com.sparrowwallet.sparrow.io.ImportException; |
|||
import com.sparrowwallet.sparrow.io.KeystoreMnemonicImport; |
|||
import com.sparrowwallet.sparrow.net.ElectrumServer; |
|||
import javafx.collections.FXCollections; |
|||
import javafx.geometry.Insets; |
|||
import javafx.geometry.Pos; |
|||
import javafx.scene.Node; |
|||
import javafx.scene.control.*; |
|||
import javafx.scene.layout.HBox; |
|||
import javafx.scene.layout.Priority; |
|||
import javafx.scene.layout.Region; |
|||
import javafx.util.StringConverter; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.stream.Collectors; |
|||
|
|||
public class MnemonicWalletKeystoreImportPane extends MnemonicKeystorePane { |
|||
private static final Logger log = LoggerFactory.getLogger(MnemonicWalletKeystoreImportPane.class); |
|||
|
|||
private final KeystoreMnemonicImport importer; |
|||
|
|||
private Button discoverButton; |
|||
private Button importButton; |
|||
|
|||
public MnemonicWalletKeystoreImportPane(KeystoreMnemonicImport importer) { |
|||
super(importer.getName(), "Seed import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png"); |
|||
this.importer = importer; |
|||
} |
|||
|
|||
@Override |
|||
protected List<Node> createRightButtons() { |
|||
discoverButton = new Button("Discover Wallet"); |
|||
discoverButton.setDisable(true); |
|||
discoverButton.setDefaultButton(true); |
|||
discoverButton.managedProperty().bind(discoverButton.visibleProperty()); |
|||
discoverButton.setOnAction(event -> { |
|||
discoverWallet(); |
|||
}); |
|||
discoverButton.managedProperty().bind(discoverButton.visibleProperty()); |
|||
discoverButton.setTooltip(new Tooltip("Look for existing transactions from the provided word list")); |
|||
discoverButton.visibleProperty().bind(AppServices.onlineProperty()); |
|||
|
|||
importButton = new Button("Import Wallet"); |
|||
importButton.setDisable(true); |
|||
importButton.managedProperty().bind(importButton.visibleProperty()); |
|||
importButton.visibleProperty().bind(discoverButton.visibleProperty().not()); |
|||
importButton.setOnAction(event -> { |
|||
setContent(getScriptTypeEntry()); |
|||
setExpanded(true); |
|||
}); |
|||
|
|||
return List.of(discoverButton, importButton); |
|||
} |
|||
|
|||
@Override |
|||
protected void onWordChange(boolean empty, boolean validWords, boolean validChecksum) { |
|||
if(!empty && validWords) { |
|||
try { |
|||
importer.getKeystore(ScriptType.P2WPKH.getDefaultDerivation(), wordEntriesProperty.get(), passphraseProperty.get()); |
|||
validChecksum = true; |
|||
} catch(ImportException e) { |
|||
if(e.getCause() instanceof MnemonicException.MnemonicTypeException) { |
|||
invalidLabel.setText("Unsupported Electrum seed"); |
|||
invalidLabel.setTooltip(new Tooltip("Seeds created in Electrum do not follow the BIP39 standard. Import the Electrum wallet file directly.")); |
|||
} else { |
|||
invalidLabel.setText("Invalid checksum"); |
|||
invalidLabel.setTooltip(null); |
|||
} |
|||
} |
|||
} |
|||
|
|||
discoverButton.setDisable(!validChecksum || !AppServices.isConnected()); |
|||
importButton.setDisable(!validChecksum); |
|||
validLabel.setVisible(validChecksum); |
|||
invalidLabel.setVisible(!validChecksum && !empty); |
|||
} |
|||
|
|||
private void discoverWallet() { |
|||
discoverButton.setDisable(true); |
|||
discoverButton.setMaxHeight(discoverButton.getHeight()); |
|||
ProgressIndicator progressIndicator = new ProgressIndicator(0); |
|||
progressIndicator.getStyleClass().add("button-progress"); |
|||
discoverButton.setGraphic(progressIndicator); |
|||
List<Wallet> wallets = new ArrayList<>(); |
|||
|
|||
List<List<ChildNumber>> derivations = ScriptType.getScriptTypesForPolicyType(PolicyType.SINGLE).stream().map(ScriptType::getDefaultDerivation).collect(Collectors.toList()); |
|||
derivations.add(List.of(new ChildNumber(0, true))); |
|||
|
|||
for(ScriptType scriptType : ScriptType.getScriptTypesForPolicyType(PolicyType.SINGLE)) { |
|||
for(List<ChildNumber> derivation : derivations) { |
|||
try { |
|||
Wallet wallet = getWallet(scriptType, derivation); |
|||
wallets.add(wallet); |
|||
} catch(ImportException e) { |
|||
String errorMessage = e.getMessage(); |
|||
if(e.getCause() instanceof MnemonicException.MnemonicChecksumException) { |
|||
errorMessage = "Invalid word list - checksum incorrect"; |
|||
} else if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) { |
|||
errorMessage = e.getCause().getMessage(); |
|||
} |
|||
setError("Import Error", errorMessage + "."); |
|||
discoverButton.setDisable(!AppServices.isConnected()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
ElectrumServer.WalletDiscoveryService walletDiscoveryService = new ElectrumServer.WalletDiscoveryService(wallets); |
|||
progressIndicator.progressProperty().bind(walletDiscoveryService.progressProperty()); |
|||
walletDiscoveryService.setOnSucceeded(successEvent -> { |
|||
discoverButton.setGraphic(null); |
|||
Optional<Wallet> optWallet = walletDiscoveryService.getValue(); |
|||
if(optWallet.isPresent()) { |
|||
EventManager.get().post(new WalletImportEvent(optWallet.get())); |
|||
} else { |
|||
discoverButton.setDisable(false); |
|||
Optional<ButtonType> optButtonType = AppServices.showErrorDialog("No existing wallet found", "Could not find a wallet with existing transactions using this mnemonic. Import this wallet anyway?", ButtonType.NO, ButtonType.YES); |
|||
if(optButtonType.isPresent() && optButtonType.get() == ButtonType.YES) { |
|||
setContent(getScriptTypeEntry()); |
|||
setExpanded(true); |
|||
} |
|||
} |
|||
}); |
|||
walletDiscoveryService.setOnFailed(failedEvent -> { |
|||
discoverButton.setGraphic(null); |
|||
log.error("Failed to discover wallets", failedEvent.getSource().getException()); |
|||
setError("Failed to discover wallets", failedEvent.getSource().getException().getMessage()); |
|||
}); |
|||
walletDiscoveryService.start(); |
|||
} |
|||
|
|||
private Wallet getWallet(ScriptType scriptType, List<ChildNumber> derivation) throws ImportException { |
|||
Wallet wallet = new Wallet(""); |
|||
wallet.setPolicyType(PolicyType.SINGLE); |
|||
wallet.setScriptType(scriptType); |
|||
Keystore keystore = importer.getKeystore(derivation, wordEntriesProperty.get(), passphraseProperty.get()); |
|||
wallet.getKeystores().add(keystore); |
|||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), 1)); |
|||
return wallet; |
|||
} |
|||
|
|||
private Node getScriptTypeEntry() { |
|||
Label label = new Label("Script Type:"); |
|||
|
|||
HBox fieldBox = new HBox(5); |
|||
fieldBox.setAlignment(Pos.CENTER_RIGHT); |
|||
ComboBox<ScriptType> scriptTypeComboBox = new ComboBox<>(FXCollections.observableArrayList(ScriptType.getAddressableScriptTypes(PolicyType.SINGLE))); |
|||
if(scriptTypeComboBox.getItems().contains(ScriptType.P2WPKH)) { |
|||
scriptTypeComboBox.setValue(ScriptType.P2WPKH); |
|||
} |
|||
scriptTypeComboBox.setConverter(new StringConverter<>() { |
|||
@Override |
|||
public String toString(ScriptType scriptType) { |
|||
return scriptType == null ? "" : scriptType.getDescription(); |
|||
} |
|||
|
|||
@Override |
|||
public ScriptType fromString(String string) { |
|||
return null; |
|||
} |
|||
}); |
|||
scriptTypeComboBox.setMaxWidth(170); |
|||
|
|||
HelpLabel helpLabel = new HelpLabel(); |
|||
helpLabel.setHelpText("P2WPKH is a Native Segwit type and is usually the best choice for new wallets.\nP2SH-P2WPKH is a Wrapped Segwit type and is a reasonable choice for the widest compatibility.\nP2PKH is a Legacy type and should be avoided for new wallets.\nFor existing wallets, be sure to choose the type that matches the wallet you are importing."); |
|||
fieldBox.getChildren().addAll(scriptTypeComboBox, helpLabel); |
|||
|
|||
Region region = new Region(); |
|||
HBox.setHgrow(region, Priority.SOMETIMES); |
|||
|
|||
Button importMnemonicButton = new Button("Import"); |
|||
importMnemonicButton.setOnAction(event -> { |
|||
showHideLink.setVisible(true); |
|||
setExpanded(false); |
|||
try { |
|||
ScriptType scriptType = scriptTypeComboBox.getValue(); |
|||
Wallet wallet = getWallet(scriptType, scriptType.getDefaultDerivation()); |
|||
EventManager.get().post(new WalletImportEvent(wallet)); |
|||
} catch(ImportException e) { |
|||
log.error("Error importing mnemonic", e); |
|||
String errorMessage = e.getMessage(); |
|||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) { |
|||
errorMessage = e.getCause().getMessage(); |
|||
} |
|||
setError("Import Error", errorMessage); |
|||
importButton.setDisable(false); |
|||
} |
|||
}); |
|||
|
|||
HBox contentBox = new HBox(); |
|||
contentBox.setAlignment(Pos.CENTER_RIGHT); |
|||
contentBox.setSpacing(20); |
|||
contentBox.getChildren().addAll(label, fieldBox, region, importMnemonicButton); |
|||
contentBox.setPadding(new Insets(10, 30, 10, 30)); |
|||
contentBox.setPrefHeight(60); |
|||
|
|||
return contentBox; |
|||
} |
|||
} |
Loading…
Reference in new issue