mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 13:16:44 +00:00
terminal - add mix selected functionality to broadcast premix transactions
This commit is contained in:
parent
b7992ae9e1
commit
871c503bc9
9 changed files with 584 additions and 88 deletions
|
@ -0,0 +1,124 @@
|
||||||
|
package com.sparrowwallet.sparrow.terminal.wallet;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.TerminalSize;
|
||||||
|
import com.googlecode.lanterna.gui2.*;
|
||||||
|
import com.samourai.whirlpool.client.wallet.beans.Tx0FeeTarget;
|
||||||
|
import com.samourai.whirlpool.client.whirlpool.beans.Pool;
|
||||||
|
import com.sparrowwallet.drongo.wallet.MixConfig;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletMasterMixConfigChangedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.UtxoEntry;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class MixDialog extends WalletDialog {
|
||||||
|
private static final List<FeePriority> FEE_PRIORITIES = List.of(new FeePriority("Low", Tx0FeeTarget.MIN), new FeePriority("Normal", Tx0FeeTarget.BLOCKS_4), new FeePriority("High", Tx0FeeTarget.BLOCKS_2));
|
||||||
|
|
||||||
|
private final String walletId;
|
||||||
|
private final List<UtxoEntry> utxoEntries;
|
||||||
|
|
||||||
|
private final TextBox scode;
|
||||||
|
private final ComboBox<FeePriority> premixPriority;
|
||||||
|
private final Label premixFeeRate;
|
||||||
|
|
||||||
|
private Pool mixPool;
|
||||||
|
|
||||||
|
public MixDialog(String walletId, WalletForm walletForm, List<UtxoEntry> utxoEntries) {
|
||||||
|
super(walletForm.getWallet().getFullDisplayName() + " Premix Config", walletForm);
|
||||||
|
|
||||||
|
this.walletId = walletId;
|
||||||
|
this.utxoEntries = utxoEntries;
|
||||||
|
|
||||||
|
setHints(List.of(Hint.CENTERED));
|
||||||
|
|
||||||
|
Wallet wallet = walletForm.getWallet();
|
||||||
|
MixConfig mixConfig = wallet.getMasterMixConfig();
|
||||||
|
|
||||||
|
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("SCODE"));
|
||||||
|
scode = new TextBox(new TerminalSize(20, 1));
|
||||||
|
mainPanel.addComponent(scode);
|
||||||
|
|
||||||
|
mainPanel.addComponent(new Label("Premix priority"));
|
||||||
|
premixPriority = new ComboBox<>();
|
||||||
|
FEE_PRIORITIES.forEach(premixPriority::addItem);
|
||||||
|
mainPanel.addComponent(premixPriority);
|
||||||
|
|
||||||
|
mainPanel.addComponent(new Label("Premix fee rate"));
|
||||||
|
premixFeeRate = new Label("");
|
||||||
|
mainPanel.addComponent(premixFeeRate);
|
||||||
|
|
||||||
|
Panel buttonPanel = new Panel();
|
||||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1));
|
||||||
|
buttonPanel.addComponent(new Button("Cancel", this::onCancel));
|
||||||
|
Button next = new Button("Next", this::onNext).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false));
|
||||||
|
buttonPanel.addComponent(next);
|
||||||
|
|
||||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
|
||||||
|
|
||||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel);
|
||||||
|
setComponent(mainPanel);
|
||||||
|
|
||||||
|
scode.setText(mixConfig.getScode() == null ? "" : mixConfig.getScode());
|
||||||
|
|
||||||
|
premixPriority.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> {
|
||||||
|
FeePriority feePriority = premixPriority.getItem(selectedIndex);
|
||||||
|
premixFeeRate.setText(SparrowMinerFeeSupplier.getFee(Integer.parseInt(feePriority.getTx0FeeTarget().getFeeTarget().getValue())) + " sats/vB");
|
||||||
|
});
|
||||||
|
premixPriority.setSelectedIndex(1);
|
||||||
|
|
||||||
|
scode.setTextChangeListener((newText, changedByUserInteraction) -> {
|
||||||
|
if(changedByUserInteraction) {
|
||||||
|
scode.setText(newText.toUpperCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
mixConfig.setScode(newText.toUpperCase(Locale.ROOT));
|
||||||
|
EventManager.get().post(new WalletMasterMixConfigChangedEvent(wallet));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNext() {
|
||||||
|
MixPoolDialog mixPoolDialog = new MixPoolDialog(walletId, getWalletForm(), utxoEntries, premixPriority.getSelectedItem().getTx0FeeTarget());
|
||||||
|
mixPool = mixPoolDialog.showDialog(SparrowTerminal.get().getGui());
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCancel() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pool showDialog(WindowBasedTextGUI textGUI) {
|
||||||
|
super.showDialog(textGUI);
|
||||||
|
return mixPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FeePriority {
|
||||||
|
private final String name;
|
||||||
|
private final Tx0FeeTarget tx0FeeTarget;
|
||||||
|
|
||||||
|
public FeePriority(String name, Tx0FeeTarget tx0FeeTarget) {
|
||||||
|
this.name = name;
|
||||||
|
this.tx0FeeTarget = tx0FeeTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tx0FeeTarget getTx0FeeTarget() {
|
||||||
|
return tx0FeeTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
package com.sparrowwallet.sparrow.terminal.wallet;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.TerminalSize;
|
||||||
|
import com.googlecode.lanterna.gui2.*;
|
||||||
|
import com.samourai.whirlpool.client.tx0.Tx0Preview;
|
||||||
|
import com.samourai.whirlpool.client.tx0.Tx0Previews;
|
||||||
|
import com.samourai.whirlpool.client.wallet.beans.Tx0FeeTarget;
|
||||||
|
import com.samourai.whirlpool.client.whirlpool.beans.Pool;
|
||||||
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
|
import com.sparrowwallet.drongo.wallet.MixConfig;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletMasterMixConfigChangedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.UtxoEntry;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.scene.control.ButtonBar;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
|
public class MixPoolDialog extends WalletDialog {
|
||||||
|
private static final DisplayPool NULL_POOL = new DisplayPool(null);
|
||||||
|
|
||||||
|
private final String walletId;
|
||||||
|
private final List<UtxoEntry> utxoEntries;
|
||||||
|
private final Tx0FeeTarget tx0FeeTarget;
|
||||||
|
|
||||||
|
private final ComboBox<DisplayPool> pool;
|
||||||
|
private final Label poolFeeLabel;
|
||||||
|
private final Label poolFee;
|
||||||
|
private final Label premixOutputs;
|
||||||
|
private final Button broadcast;
|
||||||
|
|
||||||
|
private Tx0Previews tx0Previews;
|
||||||
|
private final ObjectProperty<Tx0Preview> tx0PreviewProperty = new SimpleObjectProperty<>(null);
|
||||||
|
private Pool mixPool;
|
||||||
|
|
||||||
|
public MixPoolDialog(String walletId, WalletForm walletForm, List<UtxoEntry> utxoEntries, Tx0FeeTarget tx0FeeTarget) {
|
||||||
|
super(walletForm.getWallet().getFullDisplayName() + " Premix Pool", walletForm);
|
||||||
|
|
||||||
|
this.walletId = walletId;
|
||||||
|
this.utxoEntries = utxoEntries;
|
||||||
|
this.tx0FeeTarget = tx0FeeTarget;
|
||||||
|
|
||||||
|
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("Pool"));
|
||||||
|
pool = new ComboBox<>();
|
||||||
|
pool.addItem(NULL_POOL);
|
||||||
|
pool.setEnabled(false);
|
||||||
|
mainPanel.addComponent(pool);
|
||||||
|
|
||||||
|
poolFeeLabel = new Label("Pool fee");
|
||||||
|
poolFeeLabel.setPreferredSize(new TerminalSize(21, 1));
|
||||||
|
mainPanel.addComponent(poolFeeLabel);
|
||||||
|
poolFee = new Label("");
|
||||||
|
mainPanel.addComponent(poolFee);
|
||||||
|
|
||||||
|
mainPanel.addComponent(new Label("Premix outputs"));
|
||||||
|
premixOutputs = new Label("");
|
||||||
|
mainPanel.addComponent(premixOutputs);
|
||||||
|
|
||||||
|
Panel buttonPanel = new Panel();
|
||||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1));
|
||||||
|
buttonPanel.addComponent(new Button("Cancel", this::onCancel));
|
||||||
|
broadcast = new Button("Broadcast", this::onBroadcast).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false));
|
||||||
|
buttonPanel.addComponent(broadcast);
|
||||||
|
broadcast.setEnabled(false);
|
||||||
|
|
||||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
|
||||||
|
|
||||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel);
|
||||||
|
setComponent(mainPanel);
|
||||||
|
|
||||||
|
pool.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> {
|
||||||
|
DisplayPool selectedPool = pool.getSelectedItem();
|
||||||
|
if(selectedPool != NULL_POOL) {
|
||||||
|
UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat();
|
||||||
|
poolFee.setText(format.formatSatsValue(selectedPool.pool.getFeeValue()) + " sats");
|
||||||
|
fetchTx0Preview(selectedPool.pool);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tx0PreviewProperty.addListener((observable, oldValue, tx0Preview) -> {
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||||
|
if(tx0Preview == null) {
|
||||||
|
premixOutputs.setText("Calculating...");
|
||||||
|
broadcast.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
if(tx0Preview.getPool().getFeeValue() != tx0Preview.getTx0Data().getFeeValue()) {
|
||||||
|
poolFeeLabel.setText("Pool fee (discounted)");
|
||||||
|
} else {
|
||||||
|
poolFeeLabel.setText("Pool fee");
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat();
|
||||||
|
poolFee.setText(format.formatSatsValue(tx0Preview.getTx0Data().getFeeValue()) + " sats");
|
||||||
|
premixOutputs.setText(tx0Preview.getNbPremix() + " UTXOs");
|
||||||
|
broadcast.setEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Platform.runLater(this::fetchPools);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBroadcast() {
|
||||||
|
mixPool = tx0PreviewProperty.get() == null ? null : tx0PreviewProperty.get().getPool();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCancel() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pool showDialog(WindowBasedTextGUI textGUI) {
|
||||||
|
super.showDialog(textGUI);
|
||||||
|
return mixPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchPools() {
|
||||||
|
long totalUtxoValue = utxoEntries.stream().mapToLong(Entry::getValue).sum();
|
||||||
|
Whirlpool.PoolsService poolsService = new Whirlpool.PoolsService(AppServices.getWhirlpoolServices().getWhirlpool(walletId), totalUtxoValue);
|
||||||
|
poolsService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
List<Pool> availablePools = poolsService.getValue().stream().toList();
|
||||||
|
if(availablePools.isEmpty()) {
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> pool.setEnabled(false));
|
||||||
|
|
||||||
|
Whirlpool.PoolsService allPoolsService = new Whirlpool.PoolsService(AppServices.getWhirlpoolServices().getWhirlpool(walletId), null);
|
||||||
|
allPoolsService.setOnSucceeded(poolsStateEvent -> {
|
||||||
|
OptionalLong optMinValue = allPoolsService.getValue().stream().mapToLong(pool1 -> pool1.getPremixValueMin() + pool1.getFeeValue()).min();
|
||||||
|
if(optMinValue.isPresent() && totalUtxoValue < optMinValue.getAsLong()) {
|
||||||
|
UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat();
|
||||||
|
String satsValue = format.formatSatsValue(optMinValue.getAsLong()) + " sats";
|
||||||
|
String btcValue = format.formatBtcValue(optMinValue.getAsLong()) + " BTC";
|
||||||
|
AppServices.showErrorDialog("Insufficient UTXO Value", "No available pools. Select a value over " + (Config.get().getBitcoinUnit() == BitcoinUnit.BTC ? btcValue : satsValue) + ".");
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(this::close);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
allPoolsService.start();
|
||||||
|
} else {
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||||
|
pool.setEnabled(true);
|
||||||
|
pool.clearItems();
|
||||||
|
availablePools.stream().map(DisplayPool::new).forEach(pool::addItem);
|
||||||
|
pool.setSelectedIndex(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
poolsService.setOnFailed(workerStateEvent -> {
|
||||||
|
Throwable exception = workerStateEvent.getSource().getException();
|
||||||
|
while(exception.getCause() != null) {
|
||||||
|
exception = exception.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<ButtonType> optButton = AppServices.showErrorDialog("Error fetching pools", exception.getMessage(), ButtonType.CANCEL, new ButtonType("Retry", ButtonBar.ButtonData.APPLY));
|
||||||
|
if(optButton.isPresent()) {
|
||||||
|
if(optButton.get().getButtonData().equals(ButtonBar.ButtonData.APPLY)) {
|
||||||
|
fetchPools();
|
||||||
|
} else {
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> pool.setEnabled(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
poolsService.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchTx0Preview(Pool pool) {
|
||||||
|
MixConfig mixConfig = getWalletForm().getWallet().getMasterMixConfig();
|
||||||
|
if(mixConfig.getScode() == null) {
|
||||||
|
mixConfig.setScode("");
|
||||||
|
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(walletId);
|
||||||
|
if(tx0Previews != null && mixConfig.getScode().equals(whirlpool.getScode()) && tx0FeeTarget == whirlpool.getTx0FeeTarget()) {
|
||||||
|
Tx0Preview tx0Preview = tx0Previews.getTx0Preview(pool.getPoolId());
|
||||||
|
tx0PreviewProperty.set(tx0Preview);
|
||||||
|
} else {
|
||||||
|
tx0Previews = null;
|
||||||
|
whirlpool.setScode(mixConfig.getScode());
|
||||||
|
whirlpool.setTx0FeeTarget(tx0FeeTarget);
|
||||||
|
|
||||||
|
Whirlpool.Tx0PreviewsService tx0PreviewsService = new Whirlpool.Tx0PreviewsService(whirlpool, utxoEntries);
|
||||||
|
tx0PreviewsService.setOnRunning(workerStateEvent -> {
|
||||||
|
premixOutputs.setText("Calculating...");
|
||||||
|
tx0PreviewProperty.set(null);
|
||||||
|
});
|
||||||
|
tx0PreviewsService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
tx0Previews = tx0PreviewsService.getValue();
|
||||||
|
Tx0Preview tx0Preview = tx0Previews.getTx0Preview(pool.getPoolId());
|
||||||
|
tx0PreviewProperty.set(tx0Preview);
|
||||||
|
});
|
||||||
|
tx0PreviewsService.setOnFailed(workerStateEvent -> {
|
||||||
|
Throwable exception = workerStateEvent.getSource().getException();
|
||||||
|
while(exception.getCause() != null) {
|
||||||
|
exception = exception.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppServices.showErrorDialog("Error fetching Tx0","Error fetching Tx0: " + exception.getMessage());
|
||||||
|
});
|
||||||
|
tx0PreviewsService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DisplayPool {
|
||||||
|
private final Pool pool;
|
||||||
|
|
||||||
|
public DisplayPool(Pool pool) {
|
||||||
|
this.pool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if(pool == null) {
|
||||||
|
return "Fetching pools...";
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat();
|
||||||
|
return format.formatSatsValue(pool.getDenomination()) + " sats";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -111,7 +111,15 @@ public class SettingsDialog extends WalletDialog {
|
||||||
StandardAccount standardAccount = addAccountDialog.showDialog(SparrowTerminal.get().getGui());
|
StandardAccount standardAccount = addAccountDialog.showDialog(SparrowTerminal.get().getGui());
|
||||||
|
|
||||||
if(standardAccount != null) {
|
if(standardAccount != null) {
|
||||||
addAccount(masterWallet, standardAccount);
|
addAccount(masterWallet, standardAccount, () -> {
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||||
|
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
|
||||||
|
showSuccessDialog("Added Accounts", "Whirlpool Accounts have been successfully added.");
|
||||||
|
} else {
|
||||||
|
showSuccessDialog("Added Account", standardAccount.getName() + " has been successfully added.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,89 +263,6 @@ 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, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAndEncryptAccount(Wallet masterWallet, StandardAccount standardAccount, Key key) {
|
|
||||||
try {
|
|
||||||
addAndSaveAccount(masterWallet, standardAccount, key);
|
|
||||||
} finally {
|
|
||||||
masterWallet.encrypt(key);
|
|
||||||
key.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAndSaveAccount(Wallet masterWallet, StandardAccount standardAccount, Key key) {
|
|
||||||
List<Wallet> childWallets;
|
|
||||||
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
|
|
||||||
childWallets = 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));
|
|
||||||
childWallets = List.of(childWallet);
|
|
||||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> showSuccessDialog("Added Account", standardAccount.getName() + " has been successfully added."));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(key != null) {
|
|
||||||
for(Wallet childWallet : childWallets) {
|
|
||||||
childWallet.encrypt(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
public static List<String> splitString(String stringToSplit, int maxLength) {
|
||||||
String text = stringToSplit;
|
String text = stringToSplit;
|
||||||
List<String> lines = new ArrayList<>();
|
List<String> lines = new ArrayList<>();
|
||||||
|
|
|
@ -6,16 +6,19 @@ import com.googlecode.lanterna.gui2.*;
|
||||||
import com.googlecode.lanterna.gui2.table.Table;
|
import com.googlecode.lanterna.gui2.table.Table;
|
||||||
import com.googlecode.lanterna.gui2.table.TableModel;
|
import com.googlecode.lanterna.gui2.table.TableModel;
|
||||||
import com.samourai.whirlpool.client.wallet.beans.MixProgress;
|
import com.samourai.whirlpool.client.wallet.beans.MixProgress;
|
||||||
import com.sparrowwallet.drongo.wallet.MixConfig;
|
import com.samourai.whirlpool.client.whirlpool.beans.Pool;
|
||||||
import com.sparrowwallet.drongo.wallet.StandardAccount;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||||
|
import com.sparrowwallet.sparrow.terminal.ModalDialog;
|
||||||
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||||
import com.sparrowwallet.sparrow.terminal.wallet.table.*;
|
import com.sparrowwallet.sparrow.terminal.wallet.table.*;
|
||||||
import com.sparrowwallet.sparrow.wallet.*;
|
import com.sparrowwallet.sparrow.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.WeakChangeListener;
|
import javafx.beans.value.WeakChangeListener;
|
||||||
|
@ -23,6 +26,7 @@ import javafx.beans.value.WeakChangeListener;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class UtxosDialog extends WalletDialog {
|
public class UtxosDialog extends WalletDialog {
|
||||||
private final Label balance;
|
private final Label balance;
|
||||||
|
@ -34,6 +38,7 @@ public class UtxosDialog extends WalletDialog {
|
||||||
|
|
||||||
private Button startMix;
|
private Button startMix;
|
||||||
private Button mixTo;
|
private Button mixTo;
|
||||||
|
private Button mixSelected;
|
||||||
|
|
||||||
private final ChangeListener<Boolean> mixingOnlineListener = (observable, oldValue, newValue) -> {
|
private final ChangeListener<Boolean> mixingOnlineListener = (observable, oldValue, newValue) -> {
|
||||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> startMix.setEnabled(newValue));
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> startMix.setEnabled(newValue));
|
||||||
|
@ -75,6 +80,8 @@ public class UtxosDialog extends WalletDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startMix.setLabel(newValue ? "Stop Mixing" : "Start Mixing");
|
||||||
};
|
};
|
||||||
|
|
||||||
public UtxosDialog(WalletForm walletForm) {
|
public UtxosDialog(WalletForm walletForm) {
|
||||||
|
@ -99,6 +106,13 @@ public class UtxosDialog extends WalletDialog {
|
||||||
|
|
||||||
utxos = new Table<>(getTableColumns());
|
utxos = new Table<>(getTableColumns());
|
||||||
utxos.setTableCellRenderer(new EntryTableCellRenderer());
|
utxos.setTableCellRenderer(new EntryTableCellRenderer());
|
||||||
|
utxos.setSelectAction(() -> {
|
||||||
|
if(utxos.getTableModel().getRowCount() > utxos.getSelectedRow()) {
|
||||||
|
TableCell dateCell = utxos.getTableModel().getRow(utxos.getSelectedRow()).get(0);
|
||||||
|
dateCell.setSelected(!dateCell.isSelected());
|
||||||
|
updateMixSelectedButton();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
updateLabels(walletUtxosEntry);
|
updateLabels(walletUtxosEntry);
|
||||||
updateHistory(getWalletForm().getWalletUtxosEntry());
|
updateHistory(getWalletForm().getWalletUtxosEntry());
|
||||||
|
@ -135,8 +149,15 @@ public class UtxosDialog extends WalletDialog {
|
||||||
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
||||||
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.UTXOS)));
|
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.UTXOS)));
|
||||||
buttonPanel.addComponent(new Button("Refresh", this::onRefresh));
|
buttonPanel.addComponent(new Button("Refresh", this::onRefresh));
|
||||||
|
} else {
|
||||||
|
if(WhirlpoolServices.canWalletMix(getWalletForm().getWallet())) {
|
||||||
|
mixSelected = new Button("Mix Selected", this::mixSelected);
|
||||||
|
mixSelected.setEnabled(false);
|
||||||
|
buttonPanel.addComponent(mixSelected);
|
||||||
} else {
|
} else {
|
||||||
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
||||||
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1)));
|
||||||
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.UTXOS)));
|
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.UTXOS)));
|
||||||
|
@ -306,6 +327,61 @@ public class UtxosDialog extends WalletDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateMixSelectedButton() {
|
||||||
|
if(mixSelected == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixSelected.setEnabled(!getSelectedEntries().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UtxoEntry> getSelectedEntries() {
|
||||||
|
return utxos.getTableModel().getRows().stream().map(row -> row.get(0)).filter(TableCell::isSelected).map(dateCell -> (UtxoEntry)dateCell.getEntry()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mixSelected() {
|
||||||
|
MixDialog mixDialog = new MixDialog(getWalletForm().getMasterWalletId(), getWalletForm(), getSelectedEntries());
|
||||||
|
Pool pool = mixDialog.showDialog(SparrowTerminal.get().getGui());
|
||||||
|
|
||||||
|
if(pool != null) {
|
||||||
|
Wallet wallet = getWalletForm().getWallet();
|
||||||
|
if(wallet.isMasterWallet() && !wallet.isWhirlpoolMasterWallet()) {
|
||||||
|
addAccount(wallet, StandardAccount.WHIRLPOOL_PREMIX, () -> broadcastPremix(pool));
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> broadcastPremix(pool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastPremix(Pool pool) {
|
||||||
|
ModalDialog broadcastingDialog = new ModalDialog(getWalletForm().getWallet().getFullDisplayName(), "Broadcasting premix...");
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> SparrowTerminal.get().getGui().addWindow(broadcastingDialog));
|
||||||
|
|
||||||
|
//The WhirlpoolWallet has already been configured
|
||||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getStorage().getWalletId(getWalletForm().getMasterWallet()));
|
||||||
|
List<BlockTransactionHashIndex> utxos = getSelectedEntries().stream().map(HashIndexEntry::getHashIndex).collect(Collectors.toList());
|
||||||
|
Whirlpool.Tx0BroadcastService tx0BroadcastService = new Whirlpool.Tx0BroadcastService(whirlpool, pool, utxos);
|
||||||
|
tx0BroadcastService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
Sha256Hash txid = tx0BroadcastService.getValue();
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||||
|
SparrowTerminal.get().getGui().removeWindow(broadcastingDialog);
|
||||||
|
AppServices.showSuccessDialog("Broadcast Successful", "Premix transaction id:\n" + txid.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
tx0BroadcastService.setOnFailed(workerStateEvent -> {
|
||||||
|
Throwable exception = workerStateEvent.getSource().getException();
|
||||||
|
while(exception.getCause() != null) {
|
||||||
|
exception = exception.getCause();
|
||||||
|
}
|
||||||
|
String message = exception.getMessage();
|
||||||
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||||
|
SparrowTerminal.get().getGui().removeWindow(broadcastingDialog);
|
||||||
|
AppServices.showErrorDialog("Error broadcasting premix transaction", message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
tx0BroadcastService.start();
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletNodesChanged(WalletNodesChangedEvent event) {
|
public void walletNodesChanged(WalletNodesChangedEvent event) {
|
||||||
if(event.getWallet().equals(getWalletForm().getWallet())) {
|
if(event.getWallet().equals(getWalletForm().getWallet())) {
|
||||||
|
|
|
@ -2,23 +2,42 @@ package com.sparrowwallet.sparrow.terminal.wallet;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder;
|
||||||
import com.sparrowwallet.drongo.BitcoinUnit;
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
|
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.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
|
import com.sparrowwallet.drongo.wallet.StandardAccount;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.CurrencyRate;
|
import com.sparrowwallet.sparrow.CurrencyRate;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.UnitFormat;
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
|
import com.sparrowwallet.sparrow.event.ChildWalletsAddedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.StorageEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.TimedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.WalletHistoryClearedEvent;
|
import com.sparrowwallet.sparrow.event.WalletHistoryClearedEvent;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||||
import com.sparrowwallet.sparrow.wallet.Function;
|
import com.sparrowwallet.sparrow.wallet.Function;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
|
|
||||||
public class WalletDialog extends DialogWindow {
|
public class WalletDialog extends DialogWindow {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WalletDialog.class);
|
||||||
|
|
||||||
private final WalletForm walletForm;
|
private final WalletForm walletForm;
|
||||||
|
|
||||||
public WalletDialog(String title, WalletForm walletForm) {
|
public WalletDialog(String title, WalletForm walletForm) {
|
||||||
|
@ -52,6 +71,95 @@ public class WalletDialog extends DialogWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void addAccount(Wallet masterWallet, StandardAccount standardAccount, Runnable postAddition) {
|
||||||
|
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);
|
||||||
|
if(postAddition != null) {
|
||||||
|
postAddition.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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, null);
|
||||||
|
if(postAddition != null) {
|
||||||
|
postAddition.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAndEncryptAccount(Wallet masterWallet, StandardAccount standardAccount, Key key) {
|
||||||
|
try {
|
||||||
|
addAndSaveAccount(masterWallet, standardAccount, key);
|
||||||
|
} finally {
|
||||||
|
masterWallet.encrypt(key);
|
||||||
|
key.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAndSaveAccount(Wallet masterWallet, StandardAccount standardAccount, Key key) {
|
||||||
|
List<Wallet> childWallets;
|
||||||
|
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
|
||||||
|
childWallets = WhirlpoolServices.prepareWhirlpoolWallet(masterWallet, getWalletForm().getWalletId(), getWalletForm().getStorage());
|
||||||
|
} else {
|
||||||
|
Wallet childWallet = masterWallet.addChildWallet(standardAccount);
|
||||||
|
EventManager.get().post(new ChildWalletsAddedEvent(getWalletForm().getStorage(), masterWallet, childWallet));
|
||||||
|
childWallets = List.of(childWallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key != null) {
|
||||||
|
for(Wallet childWallet : childWallets) {
|
||||||
|
childWallet.encrypt(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected String formatBitcoinValue(long value, boolean appendUnit) {
|
protected String formatBitcoinValue(long value, boolean appendUnit) {
|
||||||
BitcoinUnit unit = Config.get().getBitcoinUnit();
|
BitcoinUnit unit = Config.get().getBitcoinUnit();
|
||||||
if(unit == null || unit.equals(BitcoinUnit.AUTO)) {
|
if(unit == null || unit.equals(BitcoinUnit.AUTO)) {
|
||||||
|
|
|
@ -18,6 +18,16 @@ public class DateTableCell extends TableCell {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String formatCell() {
|
public String formatCell() {
|
||||||
|
String unselected = formatUnselectedCell();
|
||||||
|
|
||||||
|
if(selected) {
|
||||||
|
return "(*) " + unselected.substring(Math.min(4, unselected.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return unselected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatUnselectedCell() {
|
||||||
if(entry instanceof TransactionEntry transactionEntry && transactionEntry.getBlockTransaction() != null) {
|
if(entry instanceof TransactionEntry transactionEntry && transactionEntry.getBlockTransaction() != null) {
|
||||||
if(transactionEntry.getBlockTransaction().getHeight() == -1) {
|
if(transactionEntry.getBlockTransaction().getHeight() == -1) {
|
||||||
return "Unconfirmed Parent";
|
return "Unconfirmed Parent";
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
|
|
||||||
public abstract class TableCell {
|
public abstract class TableCell {
|
||||||
protected final Entry entry;
|
protected final Entry entry;
|
||||||
|
protected boolean selected;
|
||||||
|
|
||||||
public TableCell(Entry entry) {
|
public TableCell(Entry entry) {
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
|
@ -14,4 +15,12 @@ public abstract class TableCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String formatCell();
|
public abstract String formatCell();
|
||||||
|
|
||||||
|
public boolean isSelected() {
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelected(boolean selected) {
|
||||||
|
this.selected = selected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ public class UtxoEntry extends HashIndexEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public UtxoMixData getUtxoMixData() {
|
public UtxoMixData getUtxoMixData() {
|
||||||
Wallet wallet = getUtxoEntry().getWallet().getMasterWallet();
|
Wallet wallet = getUtxoEntry().getWallet().isMasterWallet() ? getUtxoEntry().getWallet() : getUtxoEntry().getWallet().getMasterWallet();
|
||||||
if(wallet.getUtxoMixData(getHashIndex()) != null) {
|
if(wallet.getUtxoMixData(getHashIndex()) != null) {
|
||||||
return wallet.getUtxoMixData(getHashIndex());
|
return wallet.getUtxoMixData(getHashIndex());
|
||||||
}
|
}
|
||||||
|
|
|
@ -480,6 +480,10 @@ public class Whirlpool {
|
||||||
config.setScode(scode);
|
config.setScode(scode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Tx0FeeTarget getTx0FeeTarget() {
|
||||||
|
return tx0FeeTarget;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTx0FeeTarget(Tx0FeeTarget tx0FeeTarget) {
|
public void setTx0FeeTarget(Tx0FeeTarget tx0FeeTarget) {
|
||||||
this.tx0FeeTarget = tx0FeeTarget;
|
this.tx0FeeTarget = tx0FeeTarget;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue