terminal - add account

This commit is contained in:
Craig Raw 2022-10-19 18:04:55 +02:00
parent 8f165b05c7
commit 273f3043fb
7 changed files with 223 additions and 9 deletions

View file

@ -5,6 +5,7 @@ import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.screen.Screen;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config;
@ -128,6 +129,7 @@ public class SparrowTextGui extends MultiWindowTextGUI {
} else if(event.getTimeMills() < 0) {
getGUIThread().invokeLater(() -> {
statusLabel.setText(event.getStatus());
statusProgress.setValue(0);
});
} else {
getGUIThread().invokeLater(() -> {
@ -138,6 +140,7 @@ public class SparrowTextGui extends MultiWindowTextGUI {
new KeyFrame(Duration.millis(event.getTimeMills()), e -> {
getGUIThread().invokeLater(() -> {
statusLabel.setText("");
statusProgress.setValue(0);
});
}, new KeyValue(progressProperty, 1))
);
@ -163,4 +166,13 @@ public class SparrowTextGui extends MultiWindowTextGUI {
walletHistoryFinished(new WalletHistoryFinishedEvent(event.getWallet()));
statusUpdated(new StatusEvent("Error retrieving wallet history" + (Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? ", trying another server..." : "")));
}
@Subscribe
public void childWalletsAdded(ChildWalletsAddedEvent event) {
if(!event.getChildWallets().isEmpty()) {
for(Wallet childWallet : event.getChildWallets()) {
SparrowTerminal.addWallet(event.getStorage(), childWallet);
}
}
}
}

View file

@ -36,7 +36,7 @@ public class TerminalInteractionServices implements InteractionServices {
private Optional<ButtonType> showMessageDialog(String title, String content, ButtonType[] buttons) {
String formattedContent = formatLines(content, 50);
MessageDialogBuilder builder = new MessageDialogBuilder().setTitle(title).setText(formattedContent);
MessageDialogBuilder builder = new MessageDialogBuilder().setTitle(title).setText("\n" + formattedContent);
for(ButtonType buttonType : buttons) {
builder.addButton(getButton(buttonType));
}

View file

@ -0,0 +1,97 @@
package com.sparrowwallet.sparrow.terminal.wallet;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
import com.sparrowwallet.drongo.wallet.StandardAccount;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
import java.util.ArrayList;
import java.util.List;
final class AddAccountDialog extends DialogWindow {
private ComboBox<DisplayStandardAccount> standardAccounts;
private StandardAccount standardAccount;
public AddAccountDialog(Wallet wallet) {
super("Add Account");
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("Account to add"));
standardAccounts = new ComboBox<>();
mainPanel.addComponent(standardAccounts);
List<Integer> existingIndexes = new ArrayList<>();
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
existingIndexes.add(masterWallet.getAccountIndex());
for(Wallet childWallet : masterWallet.getChildWallets()) {
if(!childWallet.isNested()) {
existingIndexes.add(childWallet.getAccountIndex());
}
}
List<StandardAccount> availableAccounts = new ArrayList<>();
for(StandardAccount standardAccount : StandardAccount.values()) {
if(!existingIndexes.contains(standardAccount.getAccountNumber()) && !StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
availableAccounts.add(standardAccount);
}
}
if(WhirlpoolServices.canWalletMix(masterWallet) && !masterWallet.isWhirlpoolMasterWallet()) {
availableAccounts.add(StandardAccount.WHIRLPOOL_PREMIX);
}
availableAccounts.stream().map(DisplayStandardAccount::new).forEach(standardAccounts::addItem);
Panel buttonPanel = new Panel();
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1));
buttonPanel.addComponent(new Button("Cancel", this::onCancel));
Button okButton = new Button("Add Account", this::addAccount).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 addAccount() {
standardAccount = standardAccounts.getSelectedItem().account;
close();
}
private void onCancel() {
close();
}
@Override
public StandardAccount showDialog(WindowBasedTextGUI textGUI) {
super.showDialog(textGUI);
return standardAccount;
}
private static class DisplayStandardAccount {
private final StandardAccount account;
public DisplayStandardAccount(StandardAccount standardAccount) {
this.account = standardAccount;
}
@Override
public String toString() {
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(account)) {
return "Whirlpool Accounts";
}
return account.getName();
}
}
}

View file

@ -191,7 +191,7 @@ public class Bip39Dialog extends NewWalletDialog {
}
private static final class WordNumberDialog extends DialogWindow {
ComboBox<Integer> wordCount;
private final ComboBox<Integer> wordCount;
private Integer numberOfWords;
public WordNumberDialog() {

View file

@ -63,6 +63,7 @@ public class LoadWallet implements Runnable {
String password = builder.build().showDialog(SparrowTerminal.get().getGui());
if(password == null) {
SparrowTerminal.get().getGui().removeWindow(loadingDialog);
return;
}

View file

@ -8,25 +8,29 @@ import com.sparrowwallet.drongo.OutputDescriptor;
import com.sparrowwallet.drongo.SecureString;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.crypto.EncryptionType;
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
import com.sparrowwallet.drongo.crypto.Key;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.StandardAccount;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.RequestOpenWalletsEvent;
import com.sparrowwallet.sparrow.event.StorageEvent;
import com.sparrowwallet.sparrow.event.TimedEvent;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.io.StorageException;
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
import com.sparrowwallet.sparrow.wallet.Function;
import com.sparrowwallet.sparrow.wallet.WalletForm;
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
import static com.sparrowwallet.sparrow.AppServices.showSuccessDialog;
public class SettingsDialog extends WalletDialog {
private static final Logger log = LoggerFactory.getLogger(SettingsDialog.class);
@ -70,7 +74,11 @@ public class SettingsDialog extends WalletDialog {
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.SETTINGS)));
buttonPanel.addComponent(new Button("Advanced", this::showAdvanced).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false)));
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
if(getWalletForm().getMasterWallet().getKeystores().stream().allMatch(ks -> ks.getSource() == KeystoreSource.SW_SEED)) {
mainPanel.addComponent(new Button("Add Account", this::showAddAccount));
} else {
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
}
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel);
setComponent(mainPanel);
@ -85,6 +93,18 @@ public class SettingsDialog extends WalletDialog {
}
}
private void showAddAccount() {
Wallet openWallet = AppServices.get().getOpenWallets().entrySet().stream().filter(entry -> getWalletForm().getWalletFile().equals(entry.getValue().getWalletFile())).map(Map.Entry::getKey).findFirst().orElseThrow();
Wallet masterWallet = openWallet.isMasterWallet() ? openWallet : openWallet.getMasterWallet();
AddAccountDialog addAccountDialog = new AddAccountDialog(masterWallet);
StandardAccount standardAccount = addAccountDialog.showDialog(SparrowTerminal.get().getGui());
if(standardAccount != null) {
addAccount(masterWallet, standardAccount);
}
}
private void saveWallet(boolean changePassword, boolean suggestChangePassword) {
WalletForm walletForm = getWalletForm();
ECKey existingPubKey = walletForm.getStorage().getEncryptionPubKey();
@ -178,6 +198,86 @@ public class SettingsDialog extends WalletDialog {
}
}
private void addAccount(Wallet masterWallet, StandardAccount standardAccount) {
if(masterWallet.isEncrypted()) {
String walletId = getWalletForm().getWalletId();
TextInputDialogBuilder builder = new TextInputDialogBuilder().setTitle("Wallet Password");
builder.setDescription("Enter the wallet password:");
builder.setPasswordInput(true);
String password = builder.build().showDialog(SparrowTerminal.get().getGui());
if(password != null) {
Platform.runLater(() -> {
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(getWalletForm().getStorage(), new SecureString(password), true);
keyDerivationService.setOnSucceeded(workerStateEvent -> {
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
ECKey encryptionFullKey = keyDerivationService.getValue();
Key key = new Key(encryptionFullKey.getPrivKeyBytes(), getWalletForm().getStorage().getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
encryptionFullKey.clear();
masterWallet.decrypt(key);
addAndEncryptAccount(masterWallet, standardAccount, key);
});
keyDerivationService.setOnFailed(workerStateEvent -> {
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
if(keyDerivationService.getException() instanceof InvalidPasswordException) {
showErrorDialog("Invalid Password", "The wallet password was invalid.");
} else {
log.error("Error deriving wallet key", keyDerivationService.getException());
}
});
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
keyDerivationService.start();
});
}
} else {
Platform.runLater(() -> addAndSaveAccount(masterWallet, standardAccount));
}
}
private void addAndEncryptAccount(Wallet masterWallet, StandardAccount standardAccount, Key key) {
try {
addAndSaveAccount(masterWallet, standardAccount);
} finally {
masterWallet.encrypt(key);
for(Wallet childWallet : masterWallet.getChildWallets()) {
if(!childWallet.isNested() && !childWallet.isEncrypted()) {
childWallet.encrypt(key);
}
}
key.clear();
}
}
private void addAndSaveAccount(Wallet masterWallet, StandardAccount standardAccount) {
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
WhirlpoolServices.prepareWhirlpoolWallet(masterWallet, getWalletForm().getWalletId(), getWalletForm().getStorage());
SparrowTerminal.get().getGuiThread().invokeLater(() -> showSuccessDialog("Added Accounts", "Whirlpool Accounts have been successfully added."));
} else {
Wallet childWallet = masterWallet.addChildWallet(standardAccount);
EventManager.get().post(new ChildWalletsAddedEvent(getWalletForm().getStorage(), masterWallet, childWallet));
SparrowTerminal.get().getGuiThread().invokeLater(() -> showSuccessDialog("Added Account", standardAccount.getName() + " has been successfully added."));
}
saveChildWallets(masterWallet);
}
private void saveChildWallets(Wallet masterWallet) {
for(Wallet childWallet : masterWallet.getChildWallets()) {
if(!childWallet.isNested()) {
Storage storage = getWalletForm().getStorage();
if(!storage.isPersisted(childWallet)) {
try {
storage.saveWallet(childWallet);
} catch(Exception e) {
log.error("Error saving wallet", e);
showErrorDialog("Error saving wallet " + childWallet.getName(), e.getMessage());
}
}
}
}
}
public static List<String> splitString(String stringToSplit, int maxLength) {
String text = stringToSplit;
List<String> lines = new ArrayList<>();

View file

@ -8,6 +8,8 @@ import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
import com.sparrowwallet.sparrow.wallet.WalletForm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class WalletAccountsDialog extends DialogWindow {
@ -24,7 +26,9 @@ public class WalletAccountsDialog extends DialogWindow {
actions = new ActionListBox();
for(Wallet wallet : masterWallet.getAllWallets()) {
List<Wallet> allWallets = new ArrayList<>(masterWallet.getAllWallets());
Collections.sort(allWallets);
for(Wallet wallet : allWallets) {
actions.addItem(wallet.getDisplayName(), () -> {
close();
SparrowTerminal.get().getGuiThread().invokeLater(() -> {