mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-23 20:36:44 +00:00
terminal - create bip39 wallet
This commit is contained in:
parent
8dd1850905
commit
8eb092a8d6
6 changed files with 540 additions and 72 deletions
|
@ -4,6 +4,7 @@ import com.googlecode.lanterna.TerminalSize;
|
|||
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||
import com.googlecode.lanterna.gui2.dialogs.ActionListDialogBuilder;
|
||||
import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder;
|
||||
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
|
@ -11,8 +12,8 @@ import com.sparrowwallet.sparrow.io.Storage;
|
|||
import com.sparrowwallet.sparrow.terminal.preferences.GeneralDialog;
|
||||
import com.sparrowwallet.sparrow.terminal.preferences.ServerStatusDialog;
|
||||
import com.sparrowwallet.sparrow.terminal.preferences.ServerTypeDialog;
|
||||
import com.sparrowwallet.sparrow.terminal.wallet.Bip39Dialog;
|
||||
import com.sparrowwallet.sparrow.terminal.wallet.LoadWallet;
|
||||
import javafx.application.Platform;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
@ -25,34 +26,32 @@ public class MasterActionListBox extends ActionListBox {
|
|||
super(new TerminalSize(14, 3));
|
||||
|
||||
addItem("Wallets", () -> {
|
||||
ActionListDialogBuilder builder = new ActionListDialogBuilder();
|
||||
builder.setTitle("Wallets");
|
||||
if(Config.get().getRecentWalletFiles() != null) {
|
||||
for(int i = 0; i < Config.get().getRecentWalletFiles().size() && i < MAX_RECENT_WALLETS; i++) {
|
||||
File recentWalletFile = Config.get().getRecentWalletFiles().get(i);
|
||||
Storage storage = new Storage(recentWalletFile);
|
||||
ActionListDialogBuilder builder = new ActionListDialogBuilder();
|
||||
builder.setTitle("Wallets");
|
||||
if(Config.get().getRecentWalletFiles() != null) {
|
||||
for(int i = 0; i < Config.get().getRecentWalletFiles().size() && i < MAX_RECENT_WALLETS; i++) {
|
||||
File recentWalletFile = Config.get().getRecentWalletFiles().get(i);
|
||||
Storage storage = new Storage(recentWalletFile);
|
||||
|
||||
Optional<Wallet> optWallet = AppServices.get().getOpenWallets().entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getWalletFile().equals(recentWalletFile)).map(Map.Entry::getKey)
|
||||
.map(wallet -> wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()).findFirst();
|
||||
if(optWallet.isPresent()) {
|
||||
builder.addAction(storage.getWalletName(null) + "*", () -> LoadWallet.getOpeningDialog(storage, optWallet.get()).showDialog(SparrowTerminal.get().getGui()));
|
||||
} else {
|
||||
builder.addAction(storage.getWalletName(null), new LoadWallet(storage));
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.addAction("Open Wallet...", () -> {
|
||||
FileDialogBuilder openBuilder = new FileDialogBuilder().setTitle("Open Wallet");
|
||||
openBuilder.setShowHiddenDirectories(true);
|
||||
openBuilder.setSelectedFile(Storage.getWalletsDir());
|
||||
File file = openBuilder.build().showDialog(SparrowTerminal.get().getGui());
|
||||
if(file != null) {
|
||||
LoadWallet loadWallet = new LoadWallet(new Storage(file));
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(loadWallet);
|
||||
}
|
||||
});
|
||||
builder.build().showDialog(SparrowTerminal.get().getGui());
|
||||
Optional<Wallet> optWallet = AppServices.get().getOpenWallets().entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getWalletFile().equals(recentWalletFile)).map(Map.Entry::getKey)
|
||||
.map(wallet -> wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()).findFirst();
|
||||
if(optWallet.isPresent()) {
|
||||
builder.addAction(storage.getWalletName(null) + "*", () -> {
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> LoadWallet.getOpeningDialog(storage, optWallet.get()).showDialog(SparrowTerminal.get().getGui()));
|
||||
});
|
||||
} else {
|
||||
builder.addAction(storage.getWalletName(null), new LoadWallet(storage));
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.addAction("Open Wallet...", () -> {
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(MasterActionListBox::openWallet);
|
||||
});
|
||||
builder.addAction("Create Wallet...", () -> {
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(MasterActionListBox::createWallet);
|
||||
});
|
||||
builder.build().showDialog(SparrowTerminal.get().getGui());
|
||||
});
|
||||
|
||||
addItem("Preferences", () -> {
|
||||
|
@ -77,4 +76,36 @@ public class MasterActionListBox extends ActionListBox {
|
|||
|
||||
addItem("Quit", () -> sparrowTerminal.getGui().getMainWindow().close());
|
||||
}
|
||||
|
||||
private static void openWallet() {
|
||||
FileDialogBuilder openBuilder = new FileDialogBuilder().setTitle("Open Wallet");
|
||||
openBuilder.setShowHiddenDirectories(true);
|
||||
openBuilder.setSelectedFile(Storage.getWalletsDir());
|
||||
File file = openBuilder.build().showDialog(SparrowTerminal.get().getGui());
|
||||
if(file != null) {
|
||||
LoadWallet loadWallet = new LoadWallet(new Storage(file));
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(loadWallet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createWallet() {
|
||||
TextInputDialogBuilder newWalletNameBuilder = new TextInputDialogBuilder();
|
||||
newWalletNameBuilder.setTitle("Create Wallet");
|
||||
newWalletNameBuilder.setDescription("Enter a name for the wallet");
|
||||
newWalletNameBuilder.setValidator(content -> Storage.walletExists(content) ? "Wallet already exists" : null);
|
||||
String walletName = newWalletNameBuilder.build().showDialog(SparrowTerminal.get().getGui());
|
||||
|
||||
ActionListDialogBuilder newBuilder = new ActionListDialogBuilder();
|
||||
newBuilder.setTitle("Create Wallet");
|
||||
newBuilder.setDescription("Choose the type of wallet");
|
||||
newBuilder.addAction("Software (BIP39)", () -> {
|
||||
Bip39Dialog bip39Dialog = new Bip39Dialog(walletName);
|
||||
bip39Dialog.showDialog(SparrowTerminal.get().getGui());
|
||||
});
|
||||
newBuilder.addAction("Watch Only", () -> {
|
||||
//OutputDescriptorDialog outputDescriptorDialog = new OutputDescriptorDialog(walletName);
|
||||
|
||||
});
|
||||
newBuilder.build().showDialog(SparrowTerminal.get().getGui());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,26 @@ import com.googlecode.lanterna.screen.TerminalScreen;
|
|||
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
|
||||
import com.googlecode.lanterna.terminal.Terminal;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.SparrowWallet;
|
||||
import com.sparrowwallet.sparrow.*;
|
||||
import com.sparrowwallet.sparrow.event.OpenWalletsEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletOpenedEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletOpeningEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.terminal.wallet.WalletData;
|
||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.sparrowwallet.sparrow.terminal.MasterActionListBox.MAX_RECENT_WALLETS;
|
||||
|
||||
public class SparrowTerminal extends Application {
|
||||
private static final Logger log = LoggerFactory.getLogger(SparrowTerminal.class);
|
||||
|
@ -33,6 +41,8 @@ public class SparrowTerminal extends Application {
|
|||
|
||||
private final Map<String, WalletData> walletData = new HashMap<>();
|
||||
|
||||
private static final javafx.stage.Window DEFAULT_WINDOW = new Window() { };
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
|
@ -101,4 +111,32 @@ public class SparrowTerminal extends Application {
|
|||
public static SparrowTerminal get() {
|
||||
return sparrowTerminal;
|
||||
}
|
||||
|
||||
public static void addWallet(Storage storage, Wallet wallet) {
|
||||
if(wallet.isNested()) {
|
||||
WalletData walletData = SparrowTerminal.get().getWalletData().get(storage.getWalletId(wallet.getMasterWallet()));
|
||||
WalletForm walletForm = new WalletForm(storage, wallet);
|
||||
EventManager.get().register(walletForm);
|
||||
walletData.getWalletForm().getNestedWalletForms().add(walletForm);
|
||||
} else {
|
||||
EventManager.get().post(new WalletOpeningEvent(storage, wallet));
|
||||
|
||||
WalletForm walletForm = new WalletForm(storage, wallet);
|
||||
EventManager.get().register(walletForm);
|
||||
SparrowTerminal.get().getWalletData().put(walletForm.getWalletId(), new WalletData(walletForm));
|
||||
|
||||
List<WalletTabData> walletTabDataList = SparrowTerminal.get().getWalletData().values().stream()
|
||||
.map(data -> new WalletTabData(TabData.TabType.WALLET, data.getWalletForm())).collect(Collectors.toList());
|
||||
EventManager.get().post(new OpenWalletsEvent(DEFAULT_WINDOW, walletTabDataList));
|
||||
|
||||
Set<File> walletFiles = new LinkedHashSet<>();
|
||||
walletFiles.add(storage.getWalletFile());
|
||||
if(Config.get().getRecentWalletFiles() != null) {
|
||||
walletFiles.addAll(Config.get().getRecentWalletFiles().stream().limit(MAX_RECENT_WALLETS - 1).collect(Collectors.toList()));
|
||||
}
|
||||
Config.get().setRecentWalletFiles(Config.get().isLoadRecentWallets() ? new ArrayList<>(walletFiles) : Collections.emptyList());
|
||||
}
|
||||
|
||||
EventManager.get().post(new WalletOpenedEvent(storage, wallet));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
package com.sparrowwallet.sparrow.terminal.wallet;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.gui2.*;
|
||||
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
|
||||
import com.sparrowwallet.drongo.policy.Policy;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.io.Bip39;
|
||||
import com.sparrowwallet.sparrow.io.ImportException;
|
||||
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Bip39Dialog extends NewWalletDialog {
|
||||
private static final Logger log = LoggerFactory.getLogger(Bip39Dialog.class);
|
||||
public static final int MAX_COLUMNS = 40;
|
||||
|
||||
private final Bip39 importer = new Bip39();
|
||||
|
||||
private Wallet wallet;
|
||||
|
||||
private final String walletName;
|
||||
private final ComboBox<DisplayScriptType> scriptType;
|
||||
private final TextBox seedWords;
|
||||
private final TextBox passphrase;
|
||||
private final Button createWallet;
|
||||
|
||||
public Bip39Dialog(String walletName) {
|
||||
super("Create BIP39 Wallet - " + walletName);
|
||||
|
||||
setHints(List.of(Hint.CENTERED));
|
||||
|
||||
this.walletName = walletName;
|
||||
|
||||
Panel mainPanel = new Panel();
|
||||
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5).setVerticalSpacing(1));
|
||||
|
||||
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
|
||||
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
|
||||
|
||||
mainPanel.addComponent(new Label("Script type"));
|
||||
scriptType = new ComboBox<>();
|
||||
mainPanel.addComponent(scriptType);
|
||||
|
||||
mainPanel.addComponent(new Label("Seed words"));
|
||||
seedWords = new TextBox(new TerminalSize(MAX_COLUMNS, 5));
|
||||
mainPanel.addComponent(seedWords);
|
||||
|
||||
mainPanel.addComponent(new Label("Passphrase"));
|
||||
passphrase = new TextBox(new TerminalSize(25, 1));
|
||||
mainPanel.addComponent(passphrase);
|
||||
|
||||
Panel buttonPanel = new Panel();
|
||||
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1));
|
||||
buttonPanel.addComponent(new Button("Cancel", this::onCancel));
|
||||
createWallet = new Button("Create Wallet", this::createWallet).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false));
|
||||
createWallet.setEnabled(false);
|
||||
buttonPanel.addComponent(createWallet);
|
||||
|
||||
mainPanel.addComponent(new Button("Generate New", () -> generateNew()));
|
||||
|
||||
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel);
|
||||
setComponent(mainPanel);
|
||||
|
||||
ScriptType.getAddressableScriptTypes(PolicyType.SINGLE).stream().map(DisplayScriptType::new).forEach(scriptType::addItem);
|
||||
scriptType.setSelectedItem(new DisplayScriptType(ScriptType.P2WPKH));
|
||||
|
||||
seedWords.setTextChangeListener((newText, changedByUserInteraction) -> {
|
||||
try {
|
||||
String[] words = newText.split("[ \n]");
|
||||
importer.getKeystore(scriptType.getSelectedItem().scriptType.getDefaultDerivation(), Arrays.asList(words), passphrase.getText());
|
||||
createWallet.setEnabled(true);
|
||||
} catch(ImportException e) {
|
||||
createWallet.setEnabled(false);
|
||||
}
|
||||
|
||||
if(changedByUserInteraction) {
|
||||
List<String> lines = splitString(newText, MAX_COLUMNS);
|
||||
String splitText = lines.stream().reduce((s1, s2) -> s1 + "\n" + s2).get();
|
||||
if(!newText.equals(splitText)) {
|
||||
seedWords.setText(splitText);
|
||||
|
||||
TerminalPosition pos = seedWords.getCaretPosition();
|
||||
if(pos.getRow() == lines.size() - 2 && pos.getColumn() == lines.get(lines.size() - 2).length()) {
|
||||
seedWords.setCaretPosition(lines.size() - 1, lines.get(lines.size() - 1).length());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void generateNew() {
|
||||
WordNumberDialog wordNumberDialog = new WordNumberDialog();
|
||||
Integer numberOfWords = wordNumberDialog.showDialog(SparrowTerminal.get().getGui());
|
||||
|
||||
if(numberOfWords != null) {
|
||||
int mnemonicSeedLength = numberOfWords * 11;
|
||||
int entropyLength = mnemonicSeedLength - (mnemonicSeedLength/33);
|
||||
|
||||
SecureRandom secureRandom;
|
||||
try {
|
||||
secureRandom = SecureRandom.getInstanceStrong();
|
||||
} catch(NoSuchAlgorithmException e) {
|
||||
secureRandom = new SecureRandom();
|
||||
}
|
||||
|
||||
DeterministicSeed deterministicSeed = new DeterministicSeed(secureRandom, entropyLength, "");
|
||||
List<String> words = deterministicSeed.getMnemonicCode();
|
||||
setWords(words);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getWords() {
|
||||
return List.of(seedWords.getText().split("[ \n]"));
|
||||
}
|
||||
|
||||
private void setWords(List<String> words) {
|
||||
String text = words.stream().reduce((s1, s2) -> s1 + " " + s2).get();
|
||||
List<String> splitText = splitString(text, MAX_COLUMNS);
|
||||
seedWords.setText(splitText.stream().reduce((s1, s2) -> s1 + "\n" + s2).get());
|
||||
}
|
||||
|
||||
public static List<String> splitString(String stringToSplit, int maxLength) {
|
||||
int splitLength = maxLength - 1;
|
||||
String text = stringToSplit;
|
||||
List<String> lines = new ArrayList<>();
|
||||
while (text.length() > splitLength) {
|
||||
int spaceAt = splitLength - 1;
|
||||
// the text is too long.
|
||||
// find the last space before the maxLength
|
||||
for (int i = splitLength - 1; i > 0; i--) {
|
||||
if (Character.isWhitespace(text.charAt(i))) {
|
||||
spaceAt = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
lines.add(text.substring(0, spaceAt));
|
||||
text = text.substring(spaceAt + 1);
|
||||
}
|
||||
lines.add(text);
|
||||
return lines;
|
||||
}
|
||||
|
||||
private void createWallet() {
|
||||
close();
|
||||
|
||||
try {
|
||||
wallet = getWallet();
|
||||
discoverAndSaveWallet(wallet);
|
||||
} catch(ImportException e) {
|
||||
log.error("Cannot import wallet", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Wallet getWallet() throws ImportException {
|
||||
Wallet wallet = new Wallet(walletName);
|
||||
wallet.setPolicyType(PolicyType.SINGLE);
|
||||
wallet.setScriptType(scriptType.getSelectedItem().scriptType);
|
||||
Keystore keystore = importer.getKeystore(wallet.getScriptType().getDefaultDerivation(), getWords(), passphrase.getText());
|
||||
wallet.getKeystores().add(keystore);
|
||||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, wallet.getScriptType(), wallet.getKeystores(), 1));
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wallet showDialog(WindowBasedTextGUI textGUI) {
|
||||
super.showDialog(textGUI);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private static final class DisplayScriptType {
|
||||
private final ScriptType scriptType;
|
||||
|
||||
public DisplayScriptType(ScriptType scriptType) {
|
||||
this.scriptType = scriptType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return scriptType.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) {
|
||||
return true;
|
||||
}
|
||||
if(o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DisplayScriptType that = (DisplayScriptType) o;
|
||||
|
||||
return scriptType == that.scriptType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return scriptType.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WordNumberDialog extends DialogWindow {
|
||||
ComboBox<Integer> wordCount;
|
||||
private Integer numberOfWords;
|
||||
|
||||
public WordNumberDialog() {
|
||||
super("Generate Seed Words");
|
||||
|
||||
setHints(List.of(Hint.CENTERED));
|
||||
|
||||
Panel mainPanel = new Panel();
|
||||
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5).setVerticalSpacing(1));
|
||||
|
||||
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
|
||||
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
|
||||
|
||||
mainPanel.addComponent(new Label("Number of words"));
|
||||
wordCount = new ComboBox<>();
|
||||
mainPanel.addComponent(wordCount);
|
||||
|
||||
wordCount.addItem(24);
|
||||
wordCount.addItem(21);
|
||||
wordCount.addItem(18);
|
||||
wordCount.addItem(15);
|
||||
wordCount.addItem(12);
|
||||
|
||||
Panel buttonPanel = new Panel();
|
||||
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1));
|
||||
buttonPanel.addComponent(new Button("Cancel", this::onCancel));
|
||||
Button okButton = new Button("Ok", this::onOk).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false));
|
||||
buttonPanel.addComponent(okButton);
|
||||
|
||||
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
|
||||
|
||||
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel);
|
||||
setComponent(mainPanel);
|
||||
}
|
||||
|
||||
private void onOk() {
|
||||
numberOfWords = wordCount.getSelectedItem();
|
||||
close();
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer showDialog(WindowBasedTextGUI textGUI) {
|
||||
super.showDialog(textGUI);
|
||||
return numberOfWords;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,28 +8,20 @@ import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
|
|||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.SparrowWallet;
|
||||
import com.sparrowwallet.sparrow.TabData;
|
||||
import com.sparrowwallet.sparrow.WalletTabData;
|
||||
import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.io.StorageException;
|
||||
import com.sparrowwallet.sparrow.io.WalletAndKey;
|
||||
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.stage.Window;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||
import static com.sparrowwallet.sparrow.terminal.MasterActionListBox.MAX_RECENT_WALLETS;
|
||||
|
||||
public class LoadWallet implements Runnable {
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadWallet.class);
|
||||
|
@ -113,7 +105,7 @@ public class LoadWallet implements Runnable {
|
|||
if(!walletAndKey.getWallet().isValid()) {
|
||||
throw new IllegalStateException("Wallet file is not valid.");
|
||||
}
|
||||
addWallet(storage, walletAndKey.getWallet());
|
||||
SparrowTerminal.addWallet(storage, walletAndKey.getWallet());
|
||||
for(Map.Entry<WalletAndKey, Storage> entry : walletAndKey.getChildWallets().entrySet()) {
|
||||
openWallet(entry.getValue(), entry.getKey());
|
||||
}
|
||||
|
@ -131,34 +123,6 @@ public class LoadWallet implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
private void addWallet(Storage storage, Wallet wallet) {
|
||||
if(wallet.isNested()) {
|
||||
WalletData walletData = SparrowTerminal.get().getWalletData().get(storage.getWalletId(wallet.getMasterWallet()));
|
||||
WalletForm walletForm = new WalletForm(storage, wallet);
|
||||
EventManager.get().register(walletForm);
|
||||
walletData.getWalletForm().getNestedWalletForms().add(walletForm);
|
||||
} else {
|
||||
EventManager.get().post(new WalletOpeningEvent(storage, wallet));
|
||||
|
||||
WalletForm walletForm = new WalletForm(storage, wallet);
|
||||
EventManager.get().register(walletForm);
|
||||
SparrowTerminal.get().getWalletData().put(walletForm.getWalletId(), new WalletData(walletForm));
|
||||
|
||||
List<WalletTabData> walletTabDataList = SparrowTerminal.get().getWalletData().values().stream()
|
||||
.map(data -> new WalletTabData(TabData.TabType.WALLET, data.getWalletForm())).collect(Collectors.toList());
|
||||
EventManager.get().post(new OpenWalletsEvent(DEFAULT_WINDOW, walletTabDataList));
|
||||
|
||||
Set<File> walletFiles = new LinkedHashSet<>();
|
||||
walletFiles.add(storage.getWalletFile());
|
||||
if(Config.get().getRecentWalletFiles() != null) {
|
||||
walletFiles.addAll(Config.get().getRecentWalletFiles().stream().limit(MAX_RECENT_WALLETS - 1).collect(Collectors.toList()));
|
||||
}
|
||||
Config.get().setRecentWalletFiles(Config.get().isLoadRecentWallets() ? new ArrayList<>(walletFiles) : Collections.emptyList());
|
||||
}
|
||||
|
||||
EventManager.get().post(new WalletOpenedEvent(storage, wallet));
|
||||
}
|
||||
|
||||
public static DialogWindow getOpeningDialog(Storage storage, Wallet masterWallet) {
|
||||
if(masterWallet.getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) {
|
||||
return new WalletAccountsDialog(storage.getWalletId(masterWallet));
|
||||
|
@ -167,8 +131,6 @@ public class LoadWallet implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
private static final javafx.stage.Window DEFAULT_WINDOW = new Window() { };
|
||||
|
||||
private static final class LoadingDialog extends DialogWindow {
|
||||
public LoadingDialog(Storage storage) {
|
||||
super(storage.getWalletName(null));
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
package com.sparrowwallet.sparrow.terminal.wallet;
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.gui2.EmptySpace;
|
||||
import com.googlecode.lanterna.gui2.Label;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
|
||||
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder;
|
||||
import com.sparrowwallet.drongo.SecureString;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.crypto.EncryptionType;
|
||||
import com.sparrowwallet.drongo.crypto.Key;
|
||||
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.StorageEvent;
|
||||
import com.sparrowwallet.sparrow.event.TimedEvent;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.io.StorageException;
|
||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||
import javafx.application.Platform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||
|
||||
public class NewWalletDialog extends DialogWindow {
|
||||
private static final Logger log = LoggerFactory.getLogger(NewWalletDialog.class);
|
||||
|
||||
public NewWalletDialog(String title) {
|
||||
super(title);
|
||||
}
|
||||
|
||||
protected void discoverAndSaveWallet(Wallet wallet) {
|
||||
if(AppServices.onlineProperty().get()) {
|
||||
discoverAccounts(wallet);
|
||||
} else {
|
||||
saveWallet(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
private void discoverAccounts(Wallet wallet) {
|
||||
DiscoveringDialog discoveringDialog = new DiscoveringDialog(wallet);
|
||||
SparrowTerminal.get().getGui().addWindow(discoveringDialog);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
ElectrumServer.WalletDiscoveryService walletDiscoveryService = new ElectrumServer.WalletDiscoveryService(List.of(wallet));
|
||||
walletDiscoveryService.setOnSucceeded(successEvent -> {
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||
SparrowTerminal.get().getGui().removeWindow(discoveringDialog);
|
||||
saveWallet(wallet);
|
||||
});
|
||||
});
|
||||
walletDiscoveryService.setOnFailed(failedEvent -> {
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||
SparrowTerminal.get().getGui().removeWindow(discoveringDialog);
|
||||
saveWallet(wallet);
|
||||
});
|
||||
log.error("Failed to discover accounts", failedEvent.getSource().getException());
|
||||
});
|
||||
walletDiscoveryService.start();
|
||||
});
|
||||
}
|
||||
|
||||
private void saveWallet(Wallet wallet) {
|
||||
Storage storage = new Storage(Storage.getWalletFile(wallet.getName()));
|
||||
|
||||
TextInputDialogBuilder builder = new TextInputDialogBuilder().setTitle("Wallet Password");
|
||||
builder.setDescription(SettingsDialog.PasswordRequirement.UPDATE_NEW.getDescription());
|
||||
builder.setPasswordInput(true);
|
||||
|
||||
String password = builder.build().showDialog(SparrowTerminal.get().getGui());
|
||||
if(password != null) {
|
||||
Platform.runLater(() -> {
|
||||
if(password.length() == 0) {
|
||||
try {
|
||||
storage.setEncryptionPubKey(Storage.NO_PASSWORD_KEY);
|
||||
storage.saveWallet(wallet);
|
||||
storage.restorePublicKeysFromSeed(wallet, null);
|
||||
SparrowTerminal.addWallet(storage, wallet);
|
||||
|
||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||
storage.saveWallet(childWallet);
|
||||
storage.restorePublicKeysFromSeed(childWallet, null);
|
||||
SparrowTerminal.addWallet(storage, childWallet);
|
||||
}
|
||||
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> LoadWallet.getOpeningDialog(storage, wallet).showDialog(SparrowTerminal.get().getGui()));
|
||||
} catch(IOException | StorageException | MnemonicException e) {
|
||||
log.error("Error saving imported wallet", e);
|
||||
}
|
||||
} else {
|
||||
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(storage, new SecureString(password));
|
||||
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(Storage.getWalletFile(wallet.getName()).getAbsolutePath(), TimedEvent.Action.END, "Done"));
|
||||
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||
Key key = null;
|
||||
|
||||
try {
|
||||
ECKey encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey);
|
||||
key = new Key(encryptionFullKey.getPrivKeyBytes(), storage.getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
|
||||
wallet.encrypt(key);
|
||||
storage.setEncryptionPubKey(encryptionPubKey);
|
||||
storage.saveWallet(wallet);
|
||||
storage.restorePublicKeysFromSeed(wallet, key);
|
||||
SparrowTerminal.addWallet(storage, wallet);
|
||||
|
||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||
if(!childWallet.isNested()) {
|
||||
childWallet.encrypt(key);
|
||||
}
|
||||
storage.saveWallet(childWallet);
|
||||
storage.restorePublicKeysFromSeed(childWallet, key);
|
||||
SparrowTerminal.addWallet(storage, childWallet);
|
||||
}
|
||||
|
||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> LoadWallet.getOpeningDialog(storage, wallet).showDialog(SparrowTerminal.get().getGui()));
|
||||
} catch(IOException | StorageException | MnemonicException e) {
|
||||
log.error("Error saving imported wallet", e);
|
||||
} finally {
|
||||
encryptionFullKey.clear();
|
||||
if(key != null) {
|
||||
key.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(Storage.getWalletFile(wallet.getName()).getAbsolutePath(), TimedEvent.Action.END, "Failed"));
|
||||
showErrorDialog("Error encrypting wallet", keyDerivationService.getException().getMessage());
|
||||
});
|
||||
EventManager.get().post(new StorageEvent(Storage.getWalletFile(wallet.getName()).getAbsolutePath(), TimedEvent.Action.START, "Encrypting wallet..."));
|
||||
keyDerivationService.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DiscoveringDialog extends DialogWindow {
|
||||
public DiscoveringDialog(Wallet wallet) {
|
||||
super(wallet.getName());
|
||||
|
||||
setHints(List.of(Hint.CENTERED));
|
||||
setFixedSize(new TerminalSize(30, 5));
|
||||
|
||||
Panel mainPanel = new Panel();
|
||||
mainPanel.setLayoutManager(new LinearLayout());
|
||||
mainPanel.addComponent(new EmptySpace(), LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning, LinearLayout.GrowPolicy.CanGrow));
|
||||
|
||||
Label label = new Label("Discovering Accounts...");
|
||||
mainPanel.addComponent(label, LinearLayout.createLayoutData(LinearLayout.Alignment.Center));
|
||||
|
||||
mainPanel.addComponent(new EmptySpace(), LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning, LinearLayout.GrowPolicy.CanGrow));
|
||||
|
||||
setComponent(mainPanel);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -204,5 +204,9 @@ public class SettingsDialog extends WalletDialog {
|
|||
this.description = description;
|
||||
this.okButtonText = okButtonText;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue