mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
send controller initial, fee rates support
This commit is contained in:
parent
1d0b66c45a
commit
571c515a46
13 changed files with 462 additions and 17 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 24cde9d073da636fbc2150b7abbd50b48342e040
|
Subproject commit c4dd1cb9dd40a7a16829a00f45acbd55f63d9895
|
|
@ -88,10 +88,12 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
private ElectrumServer.ConnectionService connectionService;
|
private ElectrumServer.ConnectionService connectionService;
|
||||||
|
|
||||||
public static Integer currentBlockHeight;
|
private static Integer currentBlockHeight;
|
||||||
|
|
||||||
public static boolean showTxHexProperty;
|
public static boolean showTxHexProperty;
|
||||||
|
|
||||||
|
private static Map<Integer, Double> targetBlockFeeRates;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
|
@ -342,6 +344,10 @@ public class AppController implements Initializable {
|
||||||
return currentBlockHeight;
|
return currentBlockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<Integer, Double> getTargetBlockFeeRates() {
|
||||||
|
return targetBlockFeeRates;
|
||||||
|
}
|
||||||
|
|
||||||
public static void showErrorDialog(String title, String content) {
|
public static void showErrorDialog(String title, String content) {
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
alert.setTitle(title);
|
alert.setTitle(title);
|
||||||
|
@ -764,6 +770,7 @@ public class AppController implements Initializable {
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void newConnection(ConnectionEvent event) {
|
public void newConnection(ConnectionEvent event) {
|
||||||
currentBlockHeight = event.getBlockHeight();
|
currentBlockHeight = event.getBlockHeight();
|
||||||
|
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||||
String banner = event.getServerBanner();
|
String banner = event.getServerBanner();
|
||||||
String status = "Connected: " + (banner == null ? "Server" : banner.split(System.lineSeparator(), 2)[0]) + " at height " + event.getBlockHeight();
|
String status = "Connected: " + (banner == null ? "Server" : banner.split(System.lineSeparator(), 2)[0]) + " at height " + event.getBlockHeight();
|
||||||
EventManager.get().post(new StatusEvent(status));
|
EventManager.get().post(new StatusEvent(status));
|
||||||
|
@ -783,6 +790,11 @@ public class AppController implements Initializable {
|
||||||
EventManager.get().post(new StatusEvent(status));
|
EventManager.get().post(new StatusEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void feesUpdated(FeeRatesUpdatedEvent event) {
|
||||||
|
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void viewTransaction(ViewTransactionEvent event) {
|
public void viewTransaction(ViewTransactionEvent event) {
|
||||||
Tab tab = addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex());
|
Tab tab = addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex());
|
||||||
|
|
35
src/main/java/com/sparrowwallet/sparrow/BitcoinUnit.java
Normal file
35
src/main/java/com/sparrowwallet/sparrow/BitcoinUnit.java
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package com.sparrowwallet.sparrow;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
|
|
||||||
|
public enum BitcoinUnit {
|
||||||
|
BTC("BTC") {
|
||||||
|
@Override
|
||||||
|
public long getSatsValue(double unitValue) {
|
||||||
|
return (long)(unitValue * Transaction.SATOSHIS_PER_BITCOIN);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SATOSHIS("sats") {
|
||||||
|
@Override
|
||||||
|
public long getSatsValue(double unitValue) {
|
||||||
|
return (long)unitValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
|
||||||
|
BitcoinUnit(String label) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract long getSatsValue(double unitValue);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import javafx.beans.NamedArg;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.chart.Axis;
|
||||||
|
import javafx.scene.chart.LineChart;
|
||||||
|
import javafx.scene.chart.XYChart;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class FeeRatesChart extends LineChart<String, Number> {
|
||||||
|
private XYChart.Series<String, Number> feeRateSeries;
|
||||||
|
private Integer selectedTargetBlocks;
|
||||||
|
|
||||||
|
public FeeRatesChart(@NamedArg("xAxis") Axis<String> xAxis, @NamedArg("yAxis") Axis<Number> yAxis) {
|
||||||
|
super(xAxis, yAxis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
feeRateSeries = new XYChart.Series<>();
|
||||||
|
getData().add(feeRateSeries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(Map<Integer, Double> targetBlocksFeeRates) {
|
||||||
|
feeRateSeries.getData().clear();
|
||||||
|
|
||||||
|
for(Integer targetBlocks : targetBlocksFeeRates.keySet()) {
|
||||||
|
XYChart.Data<String, Number> data = new XYChart.Data<>(Integer.toString(targetBlocks), targetBlocksFeeRates.get(targetBlocks));
|
||||||
|
feeRateSeries.getData().add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selectedTargetBlocks != null) {
|
||||||
|
select(selectedTargetBlocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void select(Integer targetBlocks) {
|
||||||
|
Node selectedSymbol = lookup(".chart-line-symbol.selected");
|
||||||
|
if(selectedSymbol != null) {
|
||||||
|
selectedSymbol.getStyleClass().remove("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < feeRateSeries.getData().size(); i++) {
|
||||||
|
XYChart.Data<String, Number> data = feeRateSeries.getData().get(i);
|
||||||
|
Node symbol = lookup(".chart-line-symbol.data" + i);
|
||||||
|
if(symbol != null) {
|
||||||
|
if(data.getXValue().equals(targetBlocks.toString())) {
|
||||||
|
symbol.getStyleClass().add("selected");
|
||||||
|
selectedTargetBlocks = targetBlocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,6 +64,11 @@ public class UtxosChart extends BarChart<String, Number> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void select(Entry entry) {
|
public void select(Entry entry) {
|
||||||
|
Node selectedBar = lookup(".chart-bar.selected");
|
||||||
|
if(selectedBar != null) {
|
||||||
|
selectedBar.getStyleClass().remove("selected");
|
||||||
|
}
|
||||||
|
|
||||||
for(int i = 0; i < utxoSeries.getData().size(); i++) {
|
for(int i = 0; i < utxoSeries.getData().size(); i++) {
|
||||||
XYChart.Data<String, Number> data = utxoSeries.getData().get(i);
|
XYChart.Data<String, Number> data = utxoSeries.getData().get(i);
|
||||||
Node bar = lookup(".data" + i);
|
Node bar = lookup(".data" + i);
|
||||||
|
@ -71,8 +76,6 @@ public class UtxosChart extends BarChart<String, Number> {
|
||||||
if(data.getExtraValue() != null && data.getExtraValue().equals(entry)) {
|
if(data.getExtraValue() != null && data.getExtraValue().equals(entry)) {
|
||||||
bar.getStyleClass().add("selected");
|
bar.getStyleClass().add("selected");
|
||||||
this.selectedEntry = entry;
|
this.selectedEntry = entry;
|
||||||
} else {
|
|
||||||
bar.getStyleClass().remove("selected");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,16 @@ package com.sparrowwallet.sparrow.event;
|
||||||
import com.sparrowwallet.drongo.protocol.BlockHeader;
|
import com.sparrowwallet.drongo.protocol.BlockHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ConnectionEvent {
|
public class ConnectionEvent extends FeeRatesUpdatedEvent {
|
||||||
private final List<String> serverVersion;
|
private final List<String> serverVersion;
|
||||||
private final String serverBanner;
|
private final String serverBanner;
|
||||||
private final int blockHeight;
|
private final int blockHeight;
|
||||||
private final BlockHeader blockHeader;
|
private final BlockHeader blockHeader;
|
||||||
|
|
||||||
public ConnectionEvent(List<String> serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader) {
|
public ConnectionEvent(List<String> serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader, Map<Integer, Double> targetBlockFeeRates) {
|
||||||
|
super(targetBlockFeeRates);
|
||||||
this.serverVersion = serverVersion;
|
this.serverVersion = serverVersion;
|
||||||
this.serverBanner = serverBanner;
|
this.serverBanner = serverBanner;
|
||||||
this.blockHeight = blockHeight;
|
this.blockHeight = blockHeight;
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class FeeRatesUpdatedEvent {
|
||||||
|
private final Map<Integer, Double> targetBlockFeeRates;
|
||||||
|
|
||||||
|
public FeeRatesUpdatedEvent(Map<Integer, Double> targetBlockFeeRates) {
|
||||||
|
this.targetBlockFeeRates = targetBlockFeeRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Double> getTargetBlockFeeRates() {
|
||||||
|
return targetBlockFeeRates;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,9 @@ import com.sparrowwallet.drongo.wallet.*;
|
||||||
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.ConnectionEvent;
|
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.NewBlockEvent;
|
import com.sparrowwallet.sparrow.event.NewBlockEvent;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.SendController;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
|
@ -505,6 +507,23 @@ public class ElectrumServer {
|
||||||
return transactionMap;
|
return transactionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Double> getFeeEstimates(List<Integer> targetBlocks) throws ServerException {
|
||||||
|
JsonRpcClient client = new JsonRpcClient(getTransport());
|
||||||
|
BatchRequestBuilder<Integer, Double> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(Double.class);
|
||||||
|
for(Integer targetBlock : targetBlocks) {
|
||||||
|
batchRequest.add(targetBlock, "blockchain.estimatefee", targetBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, Double> targetBlocksFeeRatesBtcKb = batchRequest.execute();
|
||||||
|
|
||||||
|
Map<Integer, Double> targetBlocksFeeRatesSats = new TreeMap<>();
|
||||||
|
for(Integer target : targetBlocksFeeRatesBtcKb.keySet()) {
|
||||||
|
targetBlocksFeeRatesSats.put(target, targetBlocksFeeRatesBtcKb.get(target) * Transaction.SATOSHIS_PER_BITCOIN / 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetBlocksFeeRatesSats;
|
||||||
|
}
|
||||||
|
|
||||||
private String getScriptHash(Wallet wallet, WalletNode node) {
|
private String getScriptHash(Wallet wallet, WalletNode node) {
|
||||||
byte[] hash = Sha256Hash.hash(wallet.getOutputScript(node).getProgram());
|
byte[] hash = Sha256Hash.hash(wallet.getOutputScript(node).getProgram());
|
||||||
byte[] reversed = Utils.reverseBytes(hash);
|
byte[] reversed = Utils.reverseBytes(hash);
|
||||||
|
@ -802,15 +821,18 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ConnectionService extends ScheduledService<ConnectionEvent> implements Thread.UncaughtExceptionHandler {
|
public static class ConnectionService extends ScheduledService<FeeRatesUpdatedEvent> implements Thread.UncaughtExceptionHandler {
|
||||||
|
private static final int FEE_RATES_PERIOD = 5 * 60 * 1000;
|
||||||
|
|
||||||
private boolean firstCall = true;
|
private boolean firstCall = true;
|
||||||
private Thread reader;
|
private Thread reader;
|
||||||
private Throwable lastReaderException;
|
private Throwable lastReaderException;
|
||||||
|
private long feeRatesRetrievedAt;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Task<ConnectionEvent> createTask() {
|
protected Task<FeeRatesUpdatedEvent> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected ConnectionEvent call() throws ServerException {
|
protected FeeRatesUpdatedEvent call() throws ServerException {
|
||||||
ElectrumServer electrumServer = new ElectrumServer();
|
ElectrumServer electrumServer = new ElectrumServer();
|
||||||
if(firstCall) {
|
if(firstCall) {
|
||||||
electrumServer.connect();
|
electrumServer.connect();
|
||||||
|
@ -826,10 +848,20 @@ public class ElectrumServer {
|
||||||
BlockHeaderTip tip = electrumServer.subscribeBlockHeaders();
|
BlockHeaderTip tip = electrumServer.subscribeBlockHeaders();
|
||||||
String banner = electrumServer.getServerBanner();
|
String banner = electrumServer.getServerBanner();
|
||||||
|
|
||||||
return new ConnectionEvent(serverVersion, banner, tip.height, tip.getBlockHeader());
|
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(SendController.TARGET_BLOCKS_RANGE);
|
||||||
|
feeRatesRetrievedAt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
return new ConnectionEvent(serverVersion, banner, tip.height, tip.getBlockHeader(), blockTargetFeeRates);
|
||||||
} else {
|
} else {
|
||||||
if(reader.isAlive()) {
|
if(reader.isAlive()) {
|
||||||
electrumServer.ping();
|
electrumServer.ping();
|
||||||
|
|
||||||
|
long elapsed = System.currentTimeMillis() - feeRatesRetrievedAt;
|
||||||
|
if(elapsed > FEE_RATES_PERIOD) {
|
||||||
|
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(SendController.TARGET_BLOCKS_RANGE);
|
||||||
|
feeRatesRetrievedAt = System.currentTimeMillis();
|
||||||
|
return new FeeRatesUpdatedEvent(blockTargetFeeRates);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
firstCall = true;
|
firstCall = true;
|
||||||
throw new ServerException("Connection to server failed", lastReaderException);
|
throw new ServerException("Connection to server failed", lastReaderException);
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
|
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
import com.sparrowwallet.sparrow.AppController;
|
||||||
|
import com.sparrowwallet.sparrow.BitcoinUnit;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
||||||
|
import com.sparrowwallet.sparrow.control.CopyableTextField;
|
||||||
|
import com.sparrowwallet.sparrow.control.FeeRatesChart;
|
||||||
|
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import org.controlsfx.validation.ValidationResult;
|
||||||
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
|
import org.controlsfx.validation.Validator;
|
||||||
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CopyableTextField address;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField amount;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ComboBox<BitcoinUnit> amountUnit;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Slider targetBlocks;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CopyableLabel feeRate;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField fee;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ComboBox<BitcoinUnit> feeAmountUnit;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private FeeRatesChart feeRatesChart;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
EventManager.get().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initializeView() {
|
||||||
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
validationSupport.registerValidator(address, Validator.combine(
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Address", !isValidAddress())
|
||||||
|
));
|
||||||
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
|
|
||||||
|
amountUnit.getSelectionModel().select(0);
|
||||||
|
|
||||||
|
targetBlocks.setMin(0);
|
||||||
|
targetBlocks.setMax(TARGET_BLOCKS_RANGE.size() - 1);
|
||||||
|
targetBlocks.setMajorTickUnit(1);
|
||||||
|
targetBlocks.setMinorTickCount(0);
|
||||||
|
targetBlocks.setLabelFormatter(new StringConverter<Double>() {
|
||||||
|
@Override
|
||||||
|
public String toString(Double object) {
|
||||||
|
return Integer.toString(TARGET_BLOCKS_RANGE.get(object.intValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double fromString(String string) {
|
||||||
|
return (double)TARGET_BLOCKS_RANGE.indexOf(Integer.valueOf(string));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
targetBlocks.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
||||||
|
Integer target = getTargetBlocks();
|
||||||
|
|
||||||
|
if(targetBlocksFeeRates != null) {
|
||||||
|
setFeeRate(targetBlocksFeeRates.get(target));
|
||||||
|
feeRatesChart.select(target);
|
||||||
|
} else {
|
||||||
|
feeRate.setText("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
Tooltip tooltip = new Tooltip("Target confirmation within " + target + " blocks");
|
||||||
|
targetBlocks.setTooltip(tooltip);
|
||||||
|
|
||||||
|
//TODO: Set fee based on tx size
|
||||||
|
});
|
||||||
|
|
||||||
|
feeAmountUnit.getSelectionModel().select(1);
|
||||||
|
|
||||||
|
feeRatesChart.initialize();
|
||||||
|
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
||||||
|
if(targetBlocksFeeRates != null) {
|
||||||
|
feeRatesChart.update(targetBlocksFeeRates);
|
||||||
|
} else {
|
||||||
|
feeRate.setText("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
setTargetBlocks(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidAddress() {
|
||||||
|
try {
|
||||||
|
getAddress();
|
||||||
|
} catch (InvalidAddressException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address getAddress() throws InvalidAddressException {
|
||||||
|
return Address.fromString(address.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getTargetBlocks() {
|
||||||
|
int index = (int)targetBlocks.getValue();
|
||||||
|
return TARGET_BLOCKS_RANGE.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTargetBlocks(Integer target) {
|
||||||
|
int index = TARGET_BLOCKS_RANGE.indexOf(target);
|
||||||
|
targetBlocks.setValue(index);
|
||||||
|
feeRatesChart.select(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, Double> getTargetBlocksFeeRates() {
|
||||||
|
return AppController.getTargetBlockFeeRates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFeeRate(Double feeRateAmt) {
|
||||||
|
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxInput(ActionEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void feeRatesUpdated(FeeRatesUpdatedEvent event) {
|
||||||
|
feeRatesChart.update(event.getTargetBlockFeeRates());
|
||||||
|
feeRatesChart.select(getTargetBlocks());
|
||||||
|
setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
.address-text-field {
|
.form .fieldset:horizontal .field {
|
||||||
-fx-font-family: Courier;
|
-fx-pref-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receive-form .form .fieldset:horizontal .label-container {
|
||||||
|
-fx-pref-width: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-code {
|
.qr-code {
|
||||||
|
@ -7,11 +11,6 @@
|
||||||
-fx-padding: 20;
|
-fx-padding: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
.receive-form .form .fieldset:horizontal .label-container {
|
|
||||||
-fx-pref-width: 90px;
|
|
||||||
-fx-pref-height: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#lastUsedField .input-container {
|
#lastUsedField .input-container {
|
||||||
-fx-alignment: center-left;
|
-fx-alignment: center-left;
|
||||||
}
|
}
|
33
src/main/resources/com/sparrowwallet/sparrow/wallet/send.css
Normal file
33
src/main/resources/com/sparrowwallet/sparrow/wallet/send.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
.form .fieldset:horizontal .field {
|
||||||
|
-fx-pref-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-form .form .fieldset:horizontal .label-container {
|
||||||
|
-fx-pref-width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-field {
|
||||||
|
-fx-max-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#feeRatesChart {
|
||||||
|
-fx-max-width: 350px;
|
||||||
|
-fx-max-height: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-color0.chart-series-line {
|
||||||
|
-fx-stroke: rgba(105, 108, 119, 0.6);
|
||||||
|
-fx-stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-line-symbol {
|
||||||
|
-fx-background-color: rgba(30, 136, 207, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-line-symbol.selected {
|
||||||
|
-fx-background-color: rgba(30, 136, 207, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#feeRateField .input-container {
|
||||||
|
-fx-alignment: center-left;
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import java.util.*?>
|
||||||
|
<?import javafx.scene.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<?import tornadofx.control.Fieldset?>
|
||||||
|
<?import tornadofx.control.Form?>
|
||||||
|
<?import tornadofx.control.Field?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.CopyableTextField?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.CopyableLabel?>
|
||||||
|
<?import javafx.collections.FXCollections?>
|
||||||
|
<?import com.sparrowwallet.drongo.policy.PolicyType?>
|
||||||
|
<?import com.sparrowwallet.sparrow.BitcoinUnit?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.FeeRatesChart?>
|
||||||
|
<?import javafx.scene.chart.CategoryAxis?>
|
||||||
|
<?import javafx.scene.chart.NumberAxis?>
|
||||||
|
<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>
|
||||||
|
<GridPane styleClass="send-form" hgap="10.0" vgap="10.0">
|
||||||
|
<padding>
|
||||||
|
<Insets left="25.0" right="25.0" top="25.0" />
|
||||||
|
</padding>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints percentWidth="50" />
|
||||||
|
<ColumnConstraints percentWidth="30" />
|
||||||
|
<ColumnConstraints percentWidth="20" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints />
|
||||||
|
</rowConstraints>
|
||||||
|
<Form GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2">
|
||||||
|
<Fieldset inputGrow="SOMETIMES" text="Send">
|
||||||
|
<Field text="Pay to:">
|
||||||
|
<CopyableTextField fx:id="address" styleClass="address-text-field"/>
|
||||||
|
</Field>
|
||||||
|
<Field text="Label:">
|
||||||
|
<TextField fx:id="label" />
|
||||||
|
</Field>
|
||||||
|
<Field text="Amount:">
|
||||||
|
<TextField fx:id="amount" styleClass="amount-field" />
|
||||||
|
<ComboBox fx:id="amountUnit">
|
||||||
|
<items>
|
||||||
|
<FXCollections fx:factory="observableArrayList">
|
||||||
|
<BitcoinUnit fx:constant="BTC" />
|
||||||
|
<BitcoinUnit fx:constant="SATOSHIS" />
|
||||||
|
</FXCollections>
|
||||||
|
</items>
|
||||||
|
</ComboBox>
|
||||||
|
<Region style="-fx-pref-width: 20" />
|
||||||
|
<Button fx:id="maxButton" text="Max" onAction="#setMaxInput" />
|
||||||
|
</Field>
|
||||||
|
</Fieldset>
|
||||||
|
</Form>
|
||||||
|
<Form GridPane.columnIndex="0" GridPane.rowIndex="1">
|
||||||
|
<Fieldset inputGrow="SOMETIMES" text="Fee">
|
||||||
|
<Field text="Block Target:">
|
||||||
|
<Slider fx:id="targetBlocks" snapToTicks="true" showTickLabels="true" showTickMarks="true" />
|
||||||
|
</Field>
|
||||||
|
<Field fx:id="feeRateField" text="Rate:">
|
||||||
|
<CopyableLabel fx:id="feeRate" />
|
||||||
|
</Field>
|
||||||
|
<Field text="Fee:">
|
||||||
|
<TextField fx:id="fee" styleClass="amount-field"/>
|
||||||
|
<ComboBox fx:id="feeAmountUnit">
|
||||||
|
<items>
|
||||||
|
<FXCollections fx:factory="observableArrayList">
|
||||||
|
<BitcoinUnit fx:constant="BTC" />
|
||||||
|
<BitcoinUnit fx:constant="SATOSHIS" />
|
||||||
|
</FXCollections>
|
||||||
|
</items>
|
||||||
|
</ComboBox>
|
||||||
|
</Field>
|
||||||
|
</Fieldset>
|
||||||
|
</Form>
|
||||||
|
<AnchorPane GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.columnSpan="2">
|
||||||
|
<FeeRatesChart fx:id="feeRatesChart" legendVisible="false" AnchorPane.topAnchor="10" AnchorPane.leftAnchor="20" animated="false">
|
||||||
|
<xAxis>
|
||||||
|
<CategoryAxis side="BOTTOM" />
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<NumberAxis side="LEFT" />
|
||||||
|
</yAxis>
|
||||||
|
</FeeRatesChart>
|
||||||
|
</AnchorPane>
|
||||||
|
</GridPane>
|
||||||
|
</center>
|
||||||
|
</BorderPane>
|
|
@ -108,4 +108,8 @@
|
||||||
|
|
||||||
.unused-check {
|
.unused-check {
|
||||||
-fx-text-fill: #50a14f;
|
-fx-text-fill: #50a14f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-text-field {
|
||||||
|
-fx-font-family: Courier;
|
||||||
}
|
}
|
Loading…
Reference in a new issue