mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
tx creation support
This commit is contained in:
parent
013ed89e98
commit
48e741733b
5 changed files with 82 additions and 52 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 3ee7cd11eb31da06d79132f0023e6da7e534906d
|
Subproject commit ccf7de9f625c4cc73efc6948b3e699a7786da276
|
|
@ -4,12 +4,13 @@ import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletNode;
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletTransaction;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Map;
|
||||||
|
|
||||||
public class TransactionDiagram extends GridPane {
|
public class TransactionDiagram extends GridPane {
|
||||||
public TransactionDiagram() {
|
public TransactionDiagram() {
|
||||||
|
@ -23,25 +24,34 @@ public class TransactionDiagram extends GridPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(Wallet wallet, Collection<BlockTransactionHashIndex> inputs, Address toAddress, WalletNode changeNode, long fee) {
|
public void update(WalletTransaction walletTx) {
|
||||||
Pane inputsPane = getInputsLabels(inputs);
|
if(walletTx == null) {
|
||||||
|
getChildren().clear();
|
||||||
|
} else {
|
||||||
|
update(walletTx.getWallet(), walletTx.getSelectedUtxos(), walletTx.getRecipientAddress(), walletTx.getChangeNode(), walletTx.getFee());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(Wallet wallet, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, Address toAddress, WalletNode changeNode, long fee) {
|
||||||
|
Pane inputsPane = getInputsLabels(selectedUtxos);
|
||||||
GridPane.setConstraints(inputsPane, 0, 0);
|
GridPane.setConstraints(inputsPane, 0, 0);
|
||||||
|
|
||||||
Pane txPane = getTransactionPane();
|
Pane txPane = getTransactionPane();
|
||||||
GridPane.setConstraints(inputsPane, 2, 0);
|
GridPane.setConstraints(txPane, 2, 0);
|
||||||
|
|
||||||
Pane outputsPane = getOutputsLabels(wallet, toAddress, changeNode, fee);
|
Pane outputsPane = getOutputsLabels(wallet, toAddress, changeNode, fee);
|
||||||
GridPane.setConstraints(inputsPane, 4, 0);
|
GridPane.setConstraints(outputsPane, 4, 0);
|
||||||
|
|
||||||
getChildren().clear();
|
getChildren().clear();
|
||||||
getChildren().addAll(inputsPane, txPane, outputsPane);
|
getChildren().addAll(inputsPane, txPane, outputsPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pane getInputsLabels(Collection<BlockTransactionHashIndex> inputs) {
|
private Pane getInputsLabels(Map<BlockTransactionHashIndex, WalletNode> selectedUtxos) {
|
||||||
VBox inputsBox = new VBox();
|
VBox inputsBox = new VBox();
|
||||||
|
inputsBox.minHeightProperty().bind(minHeightProperty());
|
||||||
inputsBox.setAlignment(Pos.CENTER_RIGHT);
|
inputsBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
inputsBox.getChildren().add(createSpacer());
|
inputsBox.getChildren().add(createSpacer());
|
||||||
for(BlockTransactionHashIndex input : inputs) {
|
for(BlockTransactionHashIndex input : selectedUtxos.keySet()) {
|
||||||
String desc = input.getLabel() != null && !input.getLabel().isEmpty() ? input.getLabel() : input.getHashAsString().substring(0, 8) + "...:" + input.getIndex();
|
String desc = input.getLabel() != null && !input.getLabel().isEmpty() ? input.getLabel() : input.getHashAsString().substring(0, 8) + "...:" + input.getIndex();
|
||||||
Label label = new Label(desc);
|
Label label = new Label(desc);
|
||||||
inputsBox.getChildren().add(label);
|
inputsBox.getChildren().add(label);
|
||||||
|
|
|
@ -3,18 +3,18 @@ package com.sparrowwallet.sparrow.wallet;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.AppController;
|
import com.sparrowwallet.sparrow.AppController;
|
||||||
import com.sparrowwallet.sparrow.BitcoinUnit;
|
import com.sparrowwallet.sparrow.BitcoinUnit;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.control.CopyableTextField;
|
|
||||||
import com.sparrowwallet.sparrow.control.FeeRatesChart;
|
|
||||||
import com.sparrowwallet.sparrow.control.TextFieldValidator;
|
|
||||||
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
@ -27,11 +27,16 @@ import org.controlsfx.validation.Validator;
|
||||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class SendController extends WalletFormController implements Initializable {
|
public class SendController extends WalletFormController implements Initializable {
|
||||||
public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50);
|
public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50);
|
||||||
|
|
||||||
|
public static final double FALLBACK_FEE_RATE = 20000d / 1000;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CopyableTextField address;
|
private CopyableTextField address;
|
||||||
|
|
||||||
|
@ -60,15 +65,15 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
private FeeRatesChart feeRatesChart;
|
private FeeRatesChart feeRatesChart;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button clear;
|
private TransactionDiagram transactionDiagram;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button select;
|
private Button clear;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button create;
|
private Button create;
|
||||||
|
|
||||||
private ObservableList<BlockTransactionHashIndex> inputs;
|
private final ObjectProperty<WalletTransaction> walletTransactionProperty = new SimpleObjectProperty<>(null);
|
||||||
|
|
||||||
private final BooleanProperty insufficientInputsProperty = new SimpleBooleanProperty(false);
|
private final BooleanProperty insufficientInputsProperty = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
|
@ -144,19 +149,16 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
updateTransaction();
|
updateTransaction();
|
||||||
});
|
});
|
||||||
|
|
||||||
select.managedProperty().bind(select.visibleProperty());
|
walletTransactionProperty.addListener((observable, oldValue, newValue) -> {
|
||||||
create.managedProperty().bind(create.visibleProperty());
|
transactionDiagram.update(newValue);
|
||||||
if(inputs == null || inputs.isEmpty()) {
|
create.setDisable(false);
|
||||||
create.setVisible(false);
|
});
|
||||||
} else {
|
|
||||||
select.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addValidation() {
|
private void addValidation() {
|
||||||
ValidationSupport validationSupport = new ValidationSupport();
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
validationSupport.registerValidator(address, Validator.combine(
|
validationSupport.registerValidator(address, Validator.combine(
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Address", !isValidAddress())
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Address", !newValue.isEmpty() && !isValidAddress())
|
||||||
));
|
));
|
||||||
validationSupport.registerValidator(amount, Validator.combine(
|
validationSupport.registerValidator(amount, Validator.combine(
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", insufficientInputsProperty.get())
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", insufficientInputsProperty.get())
|
||||||
|
@ -167,32 +169,22 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
private void updateTransaction() {
|
private void updateTransaction() {
|
||||||
try {
|
try {
|
||||||
Address address = getAddress();
|
Address recipientAddress = getAddress();
|
||||||
Long amount = getAmount();
|
Long recipientAmount = getAmount();
|
||||||
if(amount != null) {
|
if(recipientAmount != null) {
|
||||||
Collection<BlockTransactionHashIndex> selectedInputs = selectInputs(amount);
|
Wallet wallet = getWalletForm().getWallet();
|
||||||
|
WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), recipientAddress, recipientAmount, getFeeRate());
|
||||||
Transaction transaction = new Transaction();
|
walletTransactionProperty.setValue(walletTransaction);
|
||||||
|
insufficientInputsProperty.set(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (InvalidAddressException e) {
|
} catch (InvalidAddressException e) {
|
||||||
//ignore
|
//ignore
|
||||||
} catch (InvalidTransactionException.InsufficientInputsException e) {
|
} catch (InsufficientFundsException e) {
|
||||||
insufficientInputsProperty.set(true);
|
insufficientInputsProperty.set(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<BlockTransactionHashIndex> selectInputs(Long targetValue) throws InvalidTransactionException.InsufficientInputsException {
|
walletTransactionProperty.setValue(null);
|
||||||
Set<BlockTransactionHashIndex> utxos = getWalletForm().getWallet().getWalletUtxos().keySet();
|
|
||||||
|
|
||||||
for(UtxoSelector utxoSelector : getUtxoSelectors()) {
|
|
||||||
Collection<BlockTransactionHashIndex> selectedInputs = utxoSelector.select(targetValue, utxos);
|
|
||||||
long total = selectedInputs.stream().mapToLong(BlockTransactionHashIndex::getValue).sum();
|
|
||||||
if(total > targetValue) {
|
|
||||||
return selectedInputs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidTransactionException.InsufficientInputsException("Not enough inputs for output value " + targetValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UtxoSelector> getUtxoSelectors() {
|
private List<UtxoSelector> getUtxoSelectors() {
|
||||||
|
@ -225,8 +217,8 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long getFee() {
|
private Long getFee() {
|
||||||
BitcoinUnit bitcoinUnit = amountUnit.getSelectionModel().getSelectedItem();
|
BitcoinUnit bitcoinUnit = feeAmountUnit.getSelectionModel().getSelectedItem();
|
||||||
if(amount.getText() != null && !amount.getText().isEmpty()) {
|
if(fee.getText() != null && !fee.getText().isEmpty()) {
|
||||||
Double fieldValue = Double.parseDouble(amount.getText());
|
Double fieldValue = Double.parseDouble(amount.getText());
|
||||||
return bitcoinUnit.getSatsValue(fieldValue);
|
return bitcoinUnit.getSatsValue(fieldValue);
|
||||||
}
|
}
|
||||||
|
@ -246,7 +238,16 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Integer, Double> getTargetBlocksFeeRates() {
|
private Map<Integer, Double> getTargetBlocksFeeRates() {
|
||||||
return AppController.getTargetBlockFeeRates();
|
Map<Integer, Double> retrievedFeeRates = AppController.getTargetBlockFeeRates();
|
||||||
|
if(retrievedFeeRates == null) {
|
||||||
|
retrievedFeeRates = TARGET_BLOCKS_RANGE.stream().collect(Collectors.toMap(java.util.function.Function.identity(), v -> FALLBACK_FEE_RATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retrievedFeeRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double getFeeRate() {
|
||||||
|
return getTargetBlocksFeeRates().get(getTargetBlocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFeeRate(Double feeRateAmt) {
|
private void setFeeRate(Double feeRateAmt) {
|
||||||
|
@ -257,6 +258,18 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clear(ActionEvent event) {
|
||||||
|
address.setText("");
|
||||||
|
label.setText("");
|
||||||
|
amount.setText("");
|
||||||
|
fee.setText("");
|
||||||
|
walletTransactionProperty.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createTransaction(ActionEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void feeRatesUpdated(FeeRatesUpdatedEvent event) {
|
public void feeRatesUpdated(FeeRatesUpdatedEvent event) {
|
||||||
feeRatesChart.update(event.getTargetBlockFeeRates());
|
feeRatesChart.update(event.getTargetBlockFeeRates());
|
||||||
|
|
|
@ -31,3 +31,7 @@
|
||||||
#feeRateField .input-container {
|
#feeRateField .input-container {
|
||||||
-fx-alignment: center-left;
|
-fx-alignment: center-left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#transactionDiagram {
|
||||||
|
-fx-min-height: 230px;
|
||||||
|
}
|
|
@ -14,11 +14,12 @@
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import com.sparrowwallet.sparrow.control.CopyableLabel?>
|
<?import com.sparrowwallet.sparrow.control.CopyableLabel?>
|
||||||
<?import javafx.collections.FXCollections?>
|
<?import javafx.collections.FXCollections?>
|
||||||
<?import com.sparrowwallet.drongo.policy.PolicyType?>
|
|
||||||
<?import com.sparrowwallet.sparrow.BitcoinUnit?>
|
<?import com.sparrowwallet.sparrow.BitcoinUnit?>
|
||||||
<?import com.sparrowwallet.sparrow.control.FeeRatesChart?>
|
<?import com.sparrowwallet.sparrow.control.FeeRatesChart?>
|
||||||
<?import javafx.scene.chart.CategoryAxis?>
|
<?import javafx.scene.chart.CategoryAxis?>
|
||||||
<?import javafx.scene.chart.NumberAxis?>
|
<?import javafx.scene.chart.NumberAxis?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.TransactionDiagram?>
|
||||||
|
|
||||||
<BorderPane stylesheets="@send.css, @wallet.css, @../script.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SendController">
|
<BorderPane stylesheets="@send.css, @wallet.css, @../script.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SendController">
|
||||||
<center>
|
<center>
|
||||||
<GridPane styleClass="send-form" hgap="10.0" vgap="10.0">
|
<GridPane styleClass="send-form" hgap="10.0" vgap="10.0">
|
||||||
|
@ -87,6 +88,9 @@
|
||||||
</yAxis>
|
</yAxis>
|
||||||
</FeeRatesChart>
|
</FeeRatesChart>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
|
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="3">
|
||||||
|
<TransactionDiagram fx:id="transactionDiagram" />
|
||||||
|
</StackPane>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
</center>
|
</center>
|
||||||
<bottom>
|
<bottom>
|
||||||
|
@ -96,9 +100,8 @@
|
||||||
</padding>
|
</padding>
|
||||||
<HBox AnchorPane.rightAnchor="10">
|
<HBox AnchorPane.rightAnchor="10">
|
||||||
<Button fx:id="clear" text="Clear" cancelButton="true" onAction="#clear" />
|
<Button fx:id="clear" text="Clear" cancelButton="true" onAction="#clear" />
|
||||||
<Region HBox.hgrow="ALWAYS" />
|
<Region HBox.hgrow="ALWAYS" style="-fx-min-width: 20px" />
|
||||||
<Button fx:id="select" text="Select UTXOs" defaultButton="true" onAction="#selectUtxos" />
|
<Button fx:id="create" text="Create Transaction" defaultButton="true" disable="true" onAction="#createTransaction" />
|
||||||
<Button fx:id="create" text="Create Transaction" defaultButton="true" onAction="#createTransaction" />
|
|
||||||
</HBox>
|
</HBox>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
</bottom>
|
</bottom>
|
||||||
|
|
Loading…
Reference in a new issue