send all utxos support

This commit is contained in:
Craig Raw 2020-07-07 07:43:01 +02:00
parent 3305d0630a
commit c8e38de5aa
8 changed files with 170 additions and 22 deletions

2
drongo

@ -1 +1 @@
Subproject commit 2ff9c94c62d1f20e408f89d7a41e2e66426b8634 Subproject commit 5d14be5c9c8cfaaa0fbf9d362b6609873dce38e0

View file

@ -4,9 +4,7 @@ import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.ReceiveActionEvent; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.event.ReceiveToEvent;
import com.sparrowwallet.sparrow.event.ViewTransactionEvent;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.wallet.*; import com.sparrowwallet.sparrow.wallet.*;
import javafx.application.Platform; import javafx.application.Platform;
@ -14,11 +12,14 @@ import javafx.geometry.Pos;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.Clipboard; import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent; import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
import org.controlsfx.glyphfont.FontAwesome; import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.List;
import java.util.stream.Collectors;
class EntryCell extends TreeTableCell<Entry, Entry> { class EntryCell extends TreeTableCell<Entry, Entry> {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@ -84,6 +85,7 @@ class EntryCell extends TreeTableCell<Entry, Entry> {
tooltip.setText(hashIndexEntry.getHashIndex().toString()); tooltip.setText(hashIndexEntry.getHashIndex().toString());
setTooltip(tooltip); setTooltip(tooltip);
HBox actionBox = new HBox();
Button viewTransactionButton = new Button(""); Button viewTransactionButton = new Button("");
Glyph searchGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SEARCH); Glyph searchGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SEARCH);
searchGlyph.setFontSize(12); searchGlyph.setFontSize(12);
@ -91,7 +93,33 @@ class EntryCell extends TreeTableCell<Entry, Entry> {
viewTransactionButton.setOnAction(event -> { viewTransactionButton.setOnAction(event -> {
EventManager.get().post(new ViewTransactionEvent(hashIndexEntry.getBlockTransaction(), hashIndexEntry)); EventManager.get().post(new ViewTransactionEvent(hashIndexEntry.getBlockTransaction(), hashIndexEntry));
}); });
setGraphic(viewTransactionButton); actionBox.getChildren().add(viewTransactionButton);
if(hashIndexEntry.getType().equals(HashIndexEntry.Type.OUTPUT) && !hashIndexEntry.isSpent()) {
Button spendUtxoButton = new Button("");
Glyph sendGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.SEND);
sendGlyph.setFontSize(12);
spendUtxoButton.setGraphic(sendGlyph);
spendUtxoButton.setOnAction(event -> {
List<HashIndexEntry> utxoEntries = getTreeTableView().getSelectionModel().getSelectedCells().stream()
.map(tp -> tp.getTreeItem().getValue())
.filter(e -> e instanceof HashIndexEntry)
.map(e -> (HashIndexEntry)e)
.filter(e -> e.getType().equals(HashIndexEntry.Type.OUTPUT) && !hashIndexEntry.isSpent())
.collect(Collectors.toList());
if(!utxoEntries.contains(hashIndexEntry)) {
utxoEntries = List.of(hashIndexEntry);
}
final List<HashIndexEntry> spendingUtxoEntries = utxoEntries;
EventManager.get().post(new SendActionEvent(utxoEntries));
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(spendingUtxoEntries)));
});
actionBox.getChildren().add(spendUtxoButton);
}
setGraphic(actionBox);
} }
} }
} }

View file

@ -0,0 +1,17 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.wallet.HashIndexEntry;
import java.util.List;
public class SendActionEvent {
private final List<HashIndexEntry> utxoEntries;
public SendActionEvent(List<HashIndexEntry> utxoEntries) {
this.utxoEntries = utxoEntries;
}
public List<HashIndexEntry> getUtxoEntries() {
return utxoEntries;
}
}

View file

@ -0,0 +1,17 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.wallet.HashIndexEntry;
import java.util.List;
public class SpendUtxoEvent {
private final List<HashIndexEntry> utxoEntries;
public SpendUtxoEvent(List<HashIndexEntry> utxoEntries) {
this.utxoEntries = utxoEntries;
}
public List<HashIndexEntry> getUtxoEntries() {
return utxoEntries;
}
}

View file

@ -0,0 +1,14 @@
package com.sparrowwallet.sparrow.wallet;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.drongo.wallet.UtxoSelector;
import java.util.Collection;
import java.util.Collections;
public class MaxUtxoSelector implements UtxoSelector {
@Override
public Collection<BlockTransactionHashIndex> select(long targetValue, Collection<BlockTransactionHashIndex> candidates) {
return Collections.unmodifiableCollection(candidates);
}
}

View file

@ -9,6 +9,7 @@ import com.sparrowwallet.sparrow.BitcoinUnit;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent; import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
import com.sparrowwallet.sparrow.event.SpendUtxoEvent;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
@ -50,6 +51,9 @@ public class SendController extends WalletFormController implements Initializabl
@FXML @FXML
private ComboBox<BitcoinUnit> amountUnit; private ComboBox<BitcoinUnit> amountUnit;
@FXML
private Button maxButton;
@FXML @FXML
private Slider targetBlocks; private Slider targetBlocks;
@ -69,10 +73,10 @@ public class SendController extends WalletFormController implements Initializabl
private TransactionDiagram transactionDiagram; private TransactionDiagram transactionDiagram;
@FXML @FXML
private Button clear; private Button clearButton;
@FXML @FXML
private Button create; private Button createButton;
private final BooleanProperty userFeeSet = new SimpleBooleanProperty(false); private final BooleanProperty userFeeSet = new SimpleBooleanProperty(false);
@ -129,6 +133,7 @@ public class SendController extends WalletFormController implements Initializabl
addValidation(); addValidation();
address.textProperty().addListener((observable, oldValue, newValue) -> { address.textProperty().addListener((observable, oldValue, newValue) -> {
maxButton.setDisable(!isValidRecipientAddress());
updateTransaction(); updateTransaction();
}); });
@ -145,6 +150,8 @@ public class SendController extends WalletFormController implements Initializabl
} }
}); });
maxButton.setDisable(!isValidRecipientAddress());
insufficientInputsProperty.addListener((observable, oldValue, newValue) -> { insufficientInputsProperty.addListener((observable, oldValue, newValue) -> {
amount.textProperty().removeListener(amountListener); amount.textProperty().removeListener(amountListener);
String amt = amount.getText(); String amt = amount.getText();
@ -193,7 +200,7 @@ public class SendController extends WalletFormController implements Initializabl
feeAmountUnit.valueProperty().addListener((observable, oldValue, newValue) -> { feeAmountUnit.valueProperty().addListener((observable, oldValue, newValue) -> {
Long value = getFeeValueSats(oldValue); Long value = getFeeValueSats(oldValue);
if(value != null) { if(value != null) {
setFee(value); setFeeValueSats(value);
} }
}); });
@ -210,20 +217,35 @@ public class SendController extends WalletFormController implements Initializabl
} }
}); });
utxoSelectorProperty.addListener((observable, oldValue, utxoSelector) -> {
if(utxoSelector instanceof PresetUtxoSelector) {
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
int num = presetUtxoSelector.getPresetUtxos().size();
String selection = " (" + num + " UTXO" + (num > 1 ? "s" : "") + " selected)";
maxButton.setText("Max" + selection);
clearButton.setText("Clear" + selection);
} else {
maxButton.setText("Max");
clearButton.setText("Clear");
}
});
walletTransactionProperty.addListener((observable, oldValue, walletTransaction) -> { walletTransactionProperty.addListener((observable, oldValue, walletTransaction) -> {
if(walletTransaction != null) { if(walletTransaction != null) {
setRecipientValueSats(walletTransaction.getRecipientAmount());
double feeRate = (double)walletTransaction.getFee() / walletTransaction.getTransaction().getVirtualSize(); double feeRate = (double)walletTransaction.getFee() / walletTransaction.getTransaction().getVirtualSize();
if(userFeeSet.get()) { if(userFeeSet.get()) {
setTargetBlocks(getTargetBlocks(feeRate)); setTargetBlocks(getTargetBlocks(feeRate));
} else { } else {
setFee(walletTransaction.getFee()); setFeeValueSats(walletTransaction.getFee());
} }
setFeeRate(feeRate); setFeeRate(feeRate);
} }
transactionDiagram.update(walletTransaction); transactionDiagram.update(walletTransaction);
create.setDisable(walletTransaction == null); createButton.setDisable(walletTransaction == null);
}); });
address.setText("32YSPMaUePf511u5adEckiNq8QLec9ksXX"); address.setText("32YSPMaUePf511u5adEckiNq8QLec9ksXX");
@ -247,12 +269,17 @@ public class SendController extends WalletFormController implements Initializabl
} }
private void updateTransaction() { private void updateTransaction() {
updateTransaction(false);
}
private void updateTransaction(boolean sendAll) {
try { try {
Address recipientAddress = getRecipientAddress(); Address recipientAddress = getRecipientAddress();
Long recipientAmount = getRecipientValueSats(); Long recipientAmount = sendAll ? Long.valueOf(1L) : getRecipientValueSats();
if(recipientAmount != null && recipientAmount != 0 && (!userFeeSet.get() || (getFeeValueSats() != null && getFeeValueSats() > 0))) { if(recipientAmount != null && recipientAmount != 0 && (!userFeeSet.get() || (getFeeValueSats() != null && getFeeValueSats() > 0))) {
Wallet wallet = getWalletForm().getWallet(); Wallet wallet = getWalletForm().getWallet();
WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), recipientAddress, recipientAmount, getFeeRate(), userFeeSet.get() ? getFeeValueSats() : null); Long userFee = userFeeSet.get() ? getFeeValueSats() : null;
WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), recipientAddress, recipientAmount, getFeeRate(), userFee, sendAll);
walletTransactionProperty.setValue(walletTransaction); walletTransactionProperty.setValue(walletTransaction);
insufficientInputsProperty.set(false); insufficientInputsProperty.set(false);
@ -268,6 +295,10 @@ public class SendController extends WalletFormController implements Initializabl
} }
private List<UtxoSelector> getUtxoSelectors() { private List<UtxoSelector> getUtxoSelectors() {
if(utxoSelectorProperty.get() != null) {
return List.of(utxoSelectorProperty.get());
}
UtxoSelector priorityUtxoSelector = new PriorityUtxoSelector(AppController.getCurrentBlockHeight()); UtxoSelector priorityUtxoSelector = new PriorityUtxoSelector(AppController.getCurrentBlockHeight());
return List.of(priorityUtxoSelector); return List.of(priorityUtxoSelector);
} }
@ -299,6 +330,14 @@ public class SendController extends WalletFormController implements Initializabl
return null; return null;
} }
private void setRecipientValueSats(long recipientValue) {
amount.textProperty().removeListener(amountListener);
DecimalFormat df = new DecimalFormat("#.#");
df.setMaximumFractionDigits(8);
amount.setText(df.format(amountUnit.getValue().getValue(recipientValue)));
amount.textProperty().addListener(amountListener);
}
private Long getFeeValueSats() { private Long getFeeValueSats() {
return getFeeValueSats(feeAmountUnit.getSelectionModel().getSelectedItem()); return getFeeValueSats(feeAmountUnit.getSelectionModel().getSelectedItem());
} }
@ -312,6 +351,14 @@ public class SendController extends WalletFormController implements Initializabl
return null; return null;
} }
private void setFeeValueSats(long feeValue) {
fee.textProperty().removeListener(feeListener);
DecimalFormat df = new DecimalFormat("#.#");
df.setMaximumFractionDigits(8);
fee.setText(df.format(feeAmountUnit.getValue().getValue(feeValue)));
fee.textProperty().addListener(feeListener);
}
private Integer getTargetBlocks() { private Integer getTargetBlocks() {
int index = (int)targetBlocks.getValue(); int index = (int)targetBlocks.getValue();
return TARGET_BLOCKS_RANGE.get(index); return TARGET_BLOCKS_RANGE.get(index);
@ -356,14 +403,6 @@ public class SendController extends WalletFormController implements Initializabl
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte"); feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte");
} }
private void setFee(long feeValue) {
fee.textProperty().removeListener(feeListener);
DecimalFormat df = new DecimalFormat("#.#");
df.setMaximumFractionDigits(8);
fee.setText(df.format(feeAmountUnit.getValue().getValue(feeValue)));
fee.textProperty().addListener(feeListener);
}
private Node getSliderThumb() { private Node getSliderThumb() {
return targetBlocks.lookup(".thumb"); return targetBlocks.lookup(".thumb");
} }
@ -373,14 +412,30 @@ public class SendController extends WalletFormController implements Initializabl
} }
public void setMaxInput(ActionEvent event) { public void setMaxInput(ActionEvent event) {
UtxoSelector utxoSelector = utxoSelectorProperty.get();
if(utxoSelector == null) {
MaxUtxoSelector maxUtxoSelector = new MaxUtxoSelector();
utxoSelectorProperty.set(maxUtxoSelector);
}
updateTransaction(true);
} }
public void clear(ActionEvent event) { public void clear(ActionEvent event) {
address.setText(""); address.setText("");
label.setText(""); label.setText("");
amount.textProperty().removeListener(amountListener);
amount.setText(""); amount.setText("");
amount.textProperty().addListener(amountListener);
fee.textProperty().removeListener(feeListener);
fee.setText(""); fee.setText("");
fee.textProperty().addListener(feeListener);
userFeeSet.set(false);
targetBlocks.setValue(4);
utxoSelectorProperty.setValue(null);
walletTransactionProperty.setValue(null); walletTransactionProperty.setValue(null);
} }
@ -394,4 +449,13 @@ public class SendController extends WalletFormController implements Initializabl
feeRatesChart.select(getTargetBlocks()); feeRatesChart.select(getTargetBlocks());
setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks())); setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks()));
} }
@Subscribe
public void spendUtxos(SpendUtxoEvent event) {
if(!event.getUtxoEntries().isEmpty() && event.getUtxoEntries().get(0).getWallet().equals(getWalletForm().getWallet())) {
List<BlockTransactionHashIndex> utxos = event.getUtxoEntries().stream().map(HashIndexEntry::getHashIndex).collect(Collectors.toList());
setUtxoSelector(new PresetUtxoSelector(utxos));
updateTransaction(true);
}
}
} }

View file

@ -4,6 +4,7 @@ import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.ReceiveActionEvent; import com.sparrowwallet.sparrow.event.ReceiveActionEvent;
import com.sparrowwallet.sparrow.event.SendActionEvent;
import com.sparrowwallet.sparrow.event.WalletSettingsChangedEvent; import com.sparrowwallet.sparrow.event.WalletSettingsChangedEvent;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -113,4 +114,11 @@ public class WalletController extends WalletFormController implements Initializa
selectFunction(Function.RECEIVE); selectFunction(Function.RECEIVE);
} }
} }
@Subscribe
public void sendAction(SendActionEvent event) {
if(!event.getUtxoEntries().isEmpty() && event.getUtxoEntries().get(0).getWallet().equals(walletForm.getWallet())) {
selectFunction(Function.SEND);
}
}
} }

View file

@ -99,9 +99,9 @@
<Insets left="25.0" right="25.0" bottom="25.0" /> <Insets left="25.0" right="25.0" bottom="25.0" />
</padding> </padding>
<HBox AnchorPane.rightAnchor="10"> <HBox AnchorPane.rightAnchor="10">
<Button fx:id="clear" text="Clear" cancelButton="true" onAction="#clear" /> <Button fx:id="clearButton" text="Clear" cancelButton="true" onAction="#clear" />
<Region HBox.hgrow="ALWAYS" style="-fx-min-width: 20px" /> <Region HBox.hgrow="ALWAYS" style="-fx-min-width: 20px" />
<Button fx:id="create" text="Create Transaction" defaultButton="true" disable="true" onAction="#createTransaction" /> <Button fx:id="createButton" text="Create Transaction" defaultButton="true" disable="true" onAction="#createTransaction" />
</HBox> </HBox>
</AnchorPane> </AnchorPane>
</bottom> </bottom>