mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-06-25 15:20:40 -04:00
Add restore from seed words section
This commit is contained in:
parent
98a1c1297d
commit
75edc3e17e
2 changed files with 123 additions and 120 deletions
|
@ -59,17 +59,20 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||||
private Preferences preferences;
|
private Preferences preferences;
|
||||||
|
|
||||||
private Button restoreButton;
|
private Button restoreButton;
|
||||||
private TextArea seedWordsTextArea;
|
private TextArea displaySeedWordsTextArea, restorSeedWordsTextArea;
|
||||||
private DatePicker datePicker;
|
private DatePicker datePicker, restoreDatePicker;
|
||||||
|
|
||||||
private int gridRow = 0;
|
private int gridRow = 0;
|
||||||
private DeterministicSeed keyChainSeed;
|
private DeterministicSeed keyChainSeed;
|
||||||
private ChangeListener<Boolean> seedWordsValidChangeListener;
|
private ChangeListener<Boolean> seedWordsValidChangeListener;
|
||||||
private SimpleBooleanProperty seedWordsValid;
|
private SimpleBooleanProperty seedWordsValid = new SimpleBooleanProperty(false);
|
||||||
private SimpleBooleanProperty dateValid;
|
private SimpleBooleanProperty dateValid = new SimpleBooleanProperty(false);
|
||||||
private ChangeListener<String> seedWordsTextAreaChangeListener;
|
private ChangeListener<String> seedWordsTextAreaChangeListener;
|
||||||
private ChangeListener<Boolean> datePickerChangeListener;
|
private ChangeListener<Boolean> datePickerChangeListener;
|
||||||
private ChangeListener<LocalDate> dateChangeListener;
|
private ChangeListener<LocalDate> dateChangeListener;
|
||||||
|
BooleanProperty seedWordsEdited = new SimpleBooleanProperty();
|
||||||
|
private String seedWordText;
|
||||||
|
private LocalDate walletCreationDate;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -85,105 +88,31 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
addTitledGroupBg(root, gridRow, 3, "Backup or restore your wallet seed words");
|
addTitledGroupBg(root, gridRow, 2, "Backup your wallet seed words");
|
||||||
seedWordsTextArea = addLabelTextArea(root, gridRow, "Wallet seed words:", "", Layout.FIRST_ROW_DISTANCE).second;
|
displaySeedWordsTextArea = addLabelTextArea(root, gridRow, "Wallet seed words:", "", Layout.FIRST_ROW_DISTANCE).second;
|
||||||
seedWordsTextArea.setPrefHeight(60);
|
displaySeedWordsTextArea.setPrefHeight(60);
|
||||||
datePicker = addLabelDatePicker(root, ++gridRow, "Creation Date:").second;
|
datePicker = addLabelDatePicker(root, ++gridRow, "Creation Date:").second;
|
||||||
|
datePicker.setMouseTransparent(true);
|
||||||
|
|
||||||
|
addTitledGroupBg(root, ++gridRow, 3, "Restore your wallet seed words", Layout.GROUP_DISTANCE);
|
||||||
|
restorSeedWordsTextArea = addLabelTextArea(root, gridRow, "Wallet seed words:", "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
||||||
|
restorSeedWordsTextArea.setPrefHeight(60);
|
||||||
|
restoreDatePicker = addLabelDatePicker(root, ++gridRow, "Creation Date:").second;
|
||||||
restoreButton = addButton(root, ++gridRow, "Restore wallet");
|
restoreButton = addButton(root, ++gridRow, "Restore wallet");
|
||||||
|
|
||||||
addTitledGroupBg(root, ++gridRow, 1, "Information", Layout.GROUP_DISTANCE);
|
addTitledGroupBg(root, ++gridRow, 1, "Information", Layout.GROUP_DISTANCE);
|
||||||
addMultilineLabel(root, gridRow, "Please write down you wallet seed words and the creation date.\n" +
|
addMultilineLabel(root, gridRow, "Please write down you wallet seed words and the creation date.\n" +
|
||||||
"You can recover your wallet with those words and the date in emergency case.",
|
"You can recover your wallet with those seed words and the creation date.",
|
||||||
Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void activate() {
|
|
||||||
seedWordsTextArea.getStyleClass().remove("validation_error");
|
|
||||||
datePicker.getStyleClass().remove("validation_error");
|
|
||||||
|
|
||||||
DeterministicSeed keyChainSeed = walletService.getWallet().getKeyChainSeed();
|
|
||||||
if (keyChainSeed.isEncrypted()) {
|
|
||||||
restoreButton.setDisable(true);
|
|
||||||
seedWordsTextArea.setDisable(true);
|
|
||||||
datePicker.setDisable(true);
|
|
||||||
askForPassword();
|
|
||||||
} else {
|
|
||||||
String key = "showSeedWordsWarning";
|
|
||||||
if (preferences.showAgain(key)) {
|
|
||||||
new Popup().warning("You have not setup a wallet password which would protect the display of the seed words.\n\n" +
|
|
||||||
"Do you want to display the seed words?")
|
|
||||||
.actionButtonText("Yes, and don't ask me again")
|
|
||||||
.onAction(() -> {
|
|
||||||
preferences.dontShowAgain(key, true);
|
|
||||||
showSeedScreen(keyChainSeed);
|
|
||||||
})
|
|
||||||
.closeButtonText("No")
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
showSeedScreen(keyChainSeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void deactivate() {
|
|
||||||
if (seedWordsTextAreaChangeListener != null)
|
|
||||||
seedWordsTextArea.textProperty().removeListener(seedWordsTextAreaChangeListener);
|
|
||||||
|
|
||||||
seedWordsTextArea.setText("");
|
|
||||||
seedWordsTextArea.getStyleClass().remove("validation_error");
|
|
||||||
|
|
||||||
if (dateChangeListener != null)
|
|
||||||
datePicker.valueProperty().removeListener(dateChangeListener);
|
|
||||||
|
|
||||||
datePicker.setValue(null);
|
|
||||||
datePicker.getStyleClass().remove("validation_error");
|
|
||||||
|
|
||||||
restoreButton.disableProperty().unbind();
|
|
||||||
|
|
||||||
if (seedWordsValid != null && seedWordsValidChangeListener != null)
|
|
||||||
seedWordsValid.removeListener(seedWordsValidChangeListener);
|
|
||||||
|
|
||||||
if (dateValid != null && datePickerChangeListener != null)
|
|
||||||
dateValid.removeListener(datePickerChangeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void askForPassword() {
|
|
||||||
walletPasswordWindow.onAesKey(aesKey -> {
|
|
||||||
Wallet wallet = walletService.getWallet();
|
|
||||||
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
|
||||||
keyChainSeed = wallet.getKeyChainSeed();
|
|
||||||
if (keyCrypter != null) {
|
|
||||||
DeterministicSeed decryptedSeed = keyChainSeed.decrypt(keyCrypter, "", aesKey);
|
|
||||||
showSeedScreen(decryptedSeed);
|
|
||||||
} else {
|
|
||||||
log.warn("keyCrypter is null");
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSeedScreen(DeterministicSeed seed) {
|
|
||||||
seedWordsTextArea.setDisable(false);
|
|
||||||
datePicker.setDisable(false);
|
|
||||||
List<String> mnemonicCode = seed.getMnemonicCode();
|
|
||||||
if (mnemonicCode != null)
|
|
||||||
seedWordsTextArea.setText(Joiner.on(" ").join(mnemonicCode));
|
|
||||||
LocalDate creationDate = Instant.ofEpochSecond(seed.getCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate();
|
|
||||||
datePicker.setValue(creationDate);
|
|
||||||
restoreButton.setOnAction(e -> onRestore());
|
|
||||||
|
|
||||||
BooleanProperty seedWordsEdited = new SimpleBooleanProperty();
|
|
||||||
|
|
||||||
seedWordsValid = new SimpleBooleanProperty(true);
|
|
||||||
seedWordsValidChangeListener = (observable, oldValue, newValue) -> {
|
seedWordsValidChangeListener = (observable, oldValue, newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
seedWordsTextArea.getStyleClass().remove("validation_error");
|
restorSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||||
} else {
|
} else {
|
||||||
seedWordsTextArea.getStyleClass().add("validation_error");
|
restorSeedWordsTextArea.getStyleClass().add("validation_error");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
seedWordsValid.addListener(seedWordsValidChangeListener);
|
|
||||||
|
|
||||||
seedWordsTextAreaChangeListener = (observable, oldValue, newValue) -> {
|
seedWordsTextAreaChangeListener = (observable, oldValue, newValue) -> {
|
||||||
seedWordsEdited.set(true);
|
seedWordsEdited.set(true);
|
||||||
|
@ -194,28 +123,108 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||||
} catch (IOException | MnemonicException e) {
|
} catch (IOException | MnemonicException e) {
|
||||||
seedWordsValid.set(false);
|
seedWordsValid.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (creationDate.equals(datePicker.getValue()))
|
|
||||||
datePicker.setValue(null);
|
|
||||||
};
|
};
|
||||||
seedWordsTextArea.textProperty().addListener(seedWordsTextAreaChangeListener);
|
|
||||||
|
|
||||||
dateValid = new SimpleBooleanProperty(true);
|
|
||||||
datePickerChangeListener = (observable, oldValue, newValue) -> {
|
datePickerChangeListener = (observable, oldValue, newValue) -> {
|
||||||
if (newValue)
|
if (newValue)
|
||||||
datePicker.getStyleClass().remove("validation_error");
|
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||||
else
|
else
|
||||||
datePicker.getStyleClass().add("validation_error");
|
restoreDatePicker.getStyleClass().add("validation_error");
|
||||||
};
|
};
|
||||||
dateValid.addListener(datePickerChangeListener);
|
|
||||||
|
|
||||||
dateChangeListener = (observable, oldValue, newValue) -> {
|
dateChangeListener = (observable, oldValue, newValue) -> {
|
||||||
dateValid.set(newValue != null && !newValue.isAfter(LocalDate.now()));
|
dateValid.set(walletCreationDate.equals(newValue));
|
||||||
};
|
};
|
||||||
datePicker.valueProperty().addListener(dateChangeListener);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void activate() {
|
||||||
|
seedWordsValid.addListener(seedWordsValidChangeListener);
|
||||||
|
dateValid.addListener(datePickerChangeListener);
|
||||||
|
restorSeedWordsTextArea.textProperty().addListener(seedWordsTextAreaChangeListener);
|
||||||
|
restoreDatePicker.valueProperty().addListener(dateChangeListener);
|
||||||
restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !dateValid.get() || !seedWordsEdited.get(),
|
restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !dateValid.get() || !seedWordsEdited.get(),
|
||||||
seedWordsValid, dateValid, seedWordsEdited));
|
seedWordsValid, dateValid, seedWordsEdited));
|
||||||
|
|
||||||
|
restoreButton.setOnAction(e -> onRestore());
|
||||||
|
|
||||||
|
restorSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||||
|
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||||
|
|
||||||
|
|
||||||
|
DeterministicSeed keyChainSeed = walletService.getWallet().getKeyChainSeed();
|
||||||
|
// wallet creation date is not encrypted
|
||||||
|
walletCreationDate = Instant.ofEpochSecond(keyChainSeed.getCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate();
|
||||||
|
log.error("walletCreationDate " + walletCreationDate);
|
||||||
|
if (keyChainSeed.isEncrypted()) {
|
||||||
|
askForPassword();
|
||||||
|
} else {
|
||||||
|
String key = "showSeedWordsWarning";
|
||||||
|
if (preferences.showAgain(key)) {
|
||||||
|
new Popup().warning("You have not setup a wallet password which would protect the display of the seed words.\n\n" +
|
||||||
|
"Do you want to display the seed words?")
|
||||||
|
.actionButtonText("Yes, and don't ask me again")
|
||||||
|
.onAction(() -> {
|
||||||
|
preferences.dontShowAgain(key, true);
|
||||||
|
initSeedWords(keyChainSeed);
|
||||||
|
showSeedScreen();
|
||||||
|
})
|
||||||
|
.closeButtonText("No")
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
initSeedWords(keyChainSeed);
|
||||||
|
showSeedScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deactivate() {
|
||||||
|
seedWordsValid.removeListener(seedWordsValidChangeListener);
|
||||||
|
dateValid.removeListener(datePickerChangeListener);
|
||||||
|
restorSeedWordsTextArea.textProperty().removeListener(seedWordsTextAreaChangeListener);
|
||||||
|
restoreDatePicker.valueProperty().removeListener(dateChangeListener);
|
||||||
|
restoreButton.disableProperty().unbind();
|
||||||
|
|
||||||
|
restoreButton.setOnAction(null);
|
||||||
|
|
||||||
|
|
||||||
|
displaySeedWordsTextArea.setText("");
|
||||||
|
restorSeedWordsTextArea.setText("");
|
||||||
|
|
||||||
|
restoreDatePicker.setValue(null);
|
||||||
|
datePicker.setValue(null);
|
||||||
|
|
||||||
|
restorSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||||
|
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void askForPassword() {
|
||||||
|
walletPasswordWindow.headLine("Enter password to view seed words").onAesKey(aesKey -> {
|
||||||
|
Wallet wallet = walletService.getWallet();
|
||||||
|
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||||
|
keyChainSeed = wallet.getKeyChainSeed();
|
||||||
|
if (keyCrypter != null) {
|
||||||
|
DeterministicSeed decryptedSeed = keyChainSeed.decrypt(keyCrypter, "", aesKey);
|
||||||
|
initSeedWords(decryptedSeed);
|
||||||
|
showSeedScreen();
|
||||||
|
} else {
|
||||||
|
log.warn("keyCrypter is null");
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSeedWords(DeterministicSeed seed) {
|
||||||
|
List<String> mnemonicCode = seed.getMnemonicCode();
|
||||||
|
if (mnemonicCode != null) {
|
||||||
|
seedWordText = Joiner.on(" ").join(mnemonicCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSeedScreen() {
|
||||||
|
displaySeedWordsTextArea.setText(seedWordText);
|
||||||
|
datePicker.setValue(walletCreationDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRestore() {
|
private void onRestore() {
|
||||||
|
@ -241,9 +250,9 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doRestore() {
|
private void doRestore() {
|
||||||
log.info("Attempting wallet restore using seed '{}' from date {}", seedWordsTextArea.getText(), datePicker.getValue());
|
log.info("Attempting wallet restore using seed '{}' from date {}", restorSeedWordsTextArea.getText(), restoreDatePicker.getValue());
|
||||||
long date = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
long date = restoreDatePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||||
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date);
|
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(restorSeedWordsTextArea.getText()), null, "", date);
|
||||||
walletService.restoreSeedWords(seed,
|
walletService.restoreSeedWords(seed,
|
||||||
() -> UserThread.execute(() -> {
|
() -> UserThread.execute(() -> {
|
||||||
log.debug("Wallet restored with seed words");
|
log.debug("Wallet restored with seed words");
|
||||||
|
@ -253,20 +262,9 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||||
"You need to shut down and restart the application.")
|
"You need to shut down and restart the application.")
|
||||||
.closeButtonText("Shut down")
|
.closeButtonText("Shut down")
|
||||||
.onClose(() -> BitsquareApp.shutDownHandler.run()).show();
|
.onClose(() -> BitsquareApp.shutDownHandler.run()).show();
|
||||||
//TODO
|
|
||||||
/* new Popup()
|
|
||||||
.information("Wallet restored successfully with the new seed words.\n\n" +
|
|
||||||
"You need to restart now the application.")
|
|
||||||
.closeButtonText("Restart")
|
|
||||||
.onClose(() -> BitsquareApp.restartDownHandler.run()).show();*/
|
|
||||||
}),
|
}),
|
||||||
throwable -> UserThread.execute(() -> {
|
throwable -> UserThread.execute(() -> {
|
||||||
log.error(throwable.getMessage());
|
log.error(throwable.getMessage());
|
||||||
new Popup()
|
|
||||||
.warning("You entered the wrong password.\n\n" +
|
|
||||||
"Please try entering your password again, carefully checking for typos or spelling errors.")
|
|
||||||
.show();
|
|
||||||
|
|
||||||
new Popup()
|
new Popup()
|
||||||
.error("An error occurred when restoring the wallet with seed words.\n" +
|
.error("An error occurred when restoring the wallet with seed words.\n" +
|
||||||
"Error message: " + throwable.getMessage())
|
"Error message: " + throwable.getMessage())
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package io.bitsquare.gui.main.overlays.windows;
|
package io.bitsquare.gui.main.overlays.windows;
|
||||||
|
|
||||||
|
import io.bitsquare.app.BitsquareApp;
|
||||||
import io.bitsquare.btc.Restrictions;
|
import io.bitsquare.btc.Restrictions;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
import io.bitsquare.common.UserThread;
|
import io.bitsquare.common.UserThread;
|
||||||
|
@ -53,7 +54,7 @@ public class EmptyWalletWindow extends Overlay<EmptyWalletWindow> {
|
||||||
private final BSFormatter formatter;
|
private final BSFormatter formatter;
|
||||||
private Button emptyWalletButton;
|
private Button emptyWalletButton;
|
||||||
private InputTextField addressInputTextField;
|
private InputTextField addressInputTextField;
|
||||||
private TextField addressTextField;
|
private TextField balanceTextField;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -97,10 +98,14 @@ public class EmptyWalletWindow extends Overlay<EmptyWalletWindow> {
|
||||||
10);
|
10);
|
||||||
|
|
||||||
Coin totalBalance = walletService.getAvailableBalance();
|
Coin totalBalance = walletService.getAvailableBalance();
|
||||||
addressTextField = addLabelTextField(gridPane, ++rowIndex, "Your available wallet balance:",
|
balanceTextField = addLabelTextField(gridPane, ++rowIndex, "Your available wallet balance:",
|
||||||
formatter.formatCoinWithCode(totalBalance), 10).second;
|
formatter.formatCoinWithCode(totalBalance), 10).second;
|
||||||
|
|
||||||
Tuple2<Label, InputTextField> tuple = addLabelInputTextField(gridPane, ++rowIndex, "Your destination address:");
|
Tuple2<Label, InputTextField> tuple = addLabelInputTextField(gridPane, ++rowIndex, "Your destination address:");
|
||||||
addressInputTextField = tuple.second;
|
addressInputTextField = tuple.second;
|
||||||
|
if (BitsquareApp.DEV_MODE)
|
||||||
|
addressInputTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi");
|
||||||
|
|
||||||
emptyWalletButton = new Button("Empty wallet");
|
emptyWalletButton = new Button("Empty wallet");
|
||||||
boolean isBalanceSufficient = Restrictions.isAboveDust(totalBalance);
|
boolean isBalanceSufficient = Restrictions.isAboveDust(totalBalance);
|
||||||
emptyWalletButton.setDefaultButton(isBalanceSufficient);
|
emptyWalletButton.setDefaultButton(isBalanceSufficient);
|
||||||
|
@ -142,13 +147,13 @@ public class EmptyWalletWindow extends Overlay<EmptyWalletWindow> {
|
||||||
aesKey,
|
aesKey,
|
||||||
() -> {
|
() -> {
|
||||||
closeButton.setText("Close");
|
closeButton.setText("Close");
|
||||||
addressTextField.setText(formatter.formatCoinWithCode(walletService.getAvailableBalance()));
|
balanceTextField.setText(formatter.formatCoinWithCode(walletService.getAvailableBalance()));
|
||||||
emptyWalletButton.setDisable(true);
|
emptyWalletButton.setDisable(true);
|
||||||
log.debug("wallet empty successful");
|
log.debug("wallet empty successful");
|
||||||
UserThread.runAfter(() -> new Popup()
|
onClose(() -> UserThread.runAfter(() -> new Popup()
|
||||||
.feedback("The balance of your wallet was successfully transferred.")
|
.feedback("The balance of your wallet was successfully transferred.")
|
||||||
.onClose(this::hide)
|
.show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS));
|
||||||
.show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS);
|
doClose();
|
||||||
},
|
},
|
||||||
(errorMessage) -> {
|
(errorMessage) -> {
|
||||||
emptyWalletButton.setDisable(false);
|
emptyWalletButton.setDisable(false);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue