add fee rate priority indicator

This commit is contained in:
Craig Raw 2021-03-08 15:25:03 +02:00
parent 13d701b0a7
commit 7ed75fc83d
5 changed files with 122 additions and 10 deletions

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.wallet.SendController;
import javafx.beans.NamedArg;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
@ -27,9 +28,11 @@ public class BlockTargetFeeRatesChart extends LineChart<String, Number> {
for(Iterator<Integer> targetBlocksIter = targetBlocksFeeRates.keySet().iterator(); targetBlocksIter.hasNext(); ) {
Integer targetBlocks = targetBlocksIter.next();
String category = targetBlocks + (targetBlocksIter.hasNext() ? "" : "+");
XYChart.Data<String, Number> data = new XYChart.Data<>(category, targetBlocksFeeRates.get(targetBlocks));
feeRateSeries.getData().add(data);
if(SendController.TARGET_BLOCKS_RANGE.contains(targetBlocks)) {
String category = targetBlocks + (targetBlocksIter.hasNext() ? "" : "+");
XYChart.Data<String, Number> data = new XYChart.Data<>(category, targetBlocksFeeRates.get(targetBlocks));
feeRateSeries.getData().add(data);
}
}
if(selectedTargetBlocks != null) {

View file

@ -40,6 +40,7 @@ public class FontAwesome5 extends GlyphFont {
LOCK_OPEN('\uf3c1'),
PEN_FANCY('\uf5ac'),
PLUS('\uf067'),
PLUS_CIRCLE('\uf055'),
QRCODE('\uf029'),
QUESTION_CIRCLE('\uf059'),
RANDOM('\uf074'),

View file

@ -40,6 +40,9 @@ public enum FeeRatesSource {
};
private static final Logger log = LoggerFactory.getLogger(FeeRatesSource.class);
public static final int BLOCKS_IN_HALF_HOUR = 3;
public static final int BLOCKS_IN_HOUR = 6;
public static final int BLOCKS_IN_TWO_HOURS = 12;
private final String name;
@ -61,16 +64,20 @@ public enum FeeRatesSource {
Gson gson = new Gson();
ThreeTierRates threeTierRates = gson.fromJson(reader, ThreeTierRates.class);
for(Integer blockTarget : defaultblockTargetFeeRates.keySet()) {
if(blockTarget < 3) {
if(blockTarget < BLOCKS_IN_HALF_HOUR) {
blockTargetFeeRates.put(blockTarget, threeTierRates.fastestFee);
} else if(blockTarget < 6) {
} else if(blockTarget < BLOCKS_IN_HOUR) {
blockTargetFeeRates.put(blockTarget, threeTierRates.halfHourFee);
} else if(blockTarget <= 10 || defaultblockTargetFeeRates.get(blockTarget) > threeTierRates.hourFee) {
} else if(blockTarget < BLOCKS_IN_TWO_HOURS || defaultblockTargetFeeRates.get(blockTarget) > threeTierRates.hourFee) {
blockTargetFeeRates.put(blockTarget, threeTierRates.hourFee);
} else {
blockTargetFeeRates.put(blockTarget, defaultblockTargetFeeRates.get(blockTarget));
}
}
if(threeTierRates.minimumFee != null) {
blockTargetFeeRates.put(Integer.MAX_VALUE, threeTierRates.minimumFee);
}
} catch (Exception e) {
log.warn("Error retrieving recommended fee rates from " + url, e);
}
@ -98,5 +105,6 @@ public enum FeeRatesSource {
Double fastestFee;
Double halfHourFee;
Double hourFee;
Double minimumFee;
}
}

View file

@ -11,9 +11,11 @@ import com.sparrowwallet.sparrow.CurrencyRate;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.net.ExchangeSource;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.net.FeeRatesSource;
import com.sparrowwallet.sparrow.net.MempoolRateSize;
import javafx.application.Platform;
import javafx.beans.property.*;
@ -28,6 +30,7 @@ import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.util.StringConverter;
import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;
@ -79,6 +82,12 @@ public class SendController extends WalletFormController implements Initializabl
@FXML
private CopyableLabel feeRate;
@FXML
private Label feeRatePriority;
@FXML
private Glyph feeRatePriorityGlyph;
@FXML
private TextField fee;
@ -350,6 +359,8 @@ public class SendController extends WalletFormController implements Initializabl
});
}
});
addFeeRangeTrackHighlight(0);
}
private void initializeTabHeader(int count) {
@ -558,10 +569,12 @@ public class SendController extends WalletFormController implements Initializabl
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
int maxTargetBlocks = 1;
for(Integer targetBlocks : targetBlocksFeeRates.keySet()) {
maxTargetBlocks = Math.max(maxTargetBlocks, targetBlocks);
Double candidate = targetBlocksFeeRates.get(targetBlocks);
if(Math.round(feeRate) >= Math.round(candidate)) {
return targetBlocks;
if(TARGET_BLOCKS_RANGE.contains(targetBlocks)) {
maxTargetBlocks = Math.max(maxTargetBlocks, targetBlocks);
Double candidate = targetBlocksFeeRates.get(targetBlocks);
if(Math.round(feeRate) >= Math.round(candidate)) {
return targetBlocks;
}
}
}
@ -623,6 +636,45 @@ public class SendController extends WalletFormController implements Initializabl
private void setFeeRate(Double feeRateAmt) {
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte");
setFeeRatePriority(feeRateAmt);
}
private void setFeeRatePriority(Double feeRateAmt) {
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
Integer targetBlocks = getTargetBlocks(feeRateAmt);
if(targetBlocksFeeRates.get(Integer.MAX_VALUE) != null) {
Double minFeeRate = targetBlocksFeeRates.get(Integer.MAX_VALUE);
if(feeRateAmt <= minFeeRate) {
feeRatePriority.setText("Below Minimum");
feeRatePriorityGlyph.setStyle("-fx-text-fill: #a0a1a7cc");
feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
return;
}
Double lowestBlocksRate = targetBlocksFeeRates.get(TARGET_BLOCKS_RANGE.get(TARGET_BLOCKS_RANGE.size() - 1));
if(lowestBlocksRate > minFeeRate && feeRateAmt < (minFeeRate + ((lowestBlocksRate - minFeeRate) / 2))) {
feeRatePriority.setText("Try Then Replace");
feeRatePriorityGlyph.setStyle("-fx-text-fill: #7eb7c9cc");
feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.PLUS_CIRCLE);
return;
}
}
if(targetBlocks != null) {
if(targetBlocks < FeeRatesSource.BLOCKS_IN_HALF_HOUR) {
feeRatePriority.setText("High Priority");
feeRatePriorityGlyph.setStyle("-fx-text-fill: #c8416499");
feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.CIRCLE);
} else if(targetBlocks < FeeRatesSource.BLOCKS_IN_HOUR) {
feeRatePriority.setText("Medium Priority");
feeRatePriorityGlyph.setStyle("-fx-text-fill: #fba71b99");
feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.CIRCLE);
} else {
feeRatePriority.setText("Low Priority");
feeRatePriorityGlyph.setStyle("-fx-text-fill: #41a9c999");
feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.CIRCLE);
}
}
}
private Node getSliderThumb() {
@ -635,6 +687,47 @@ public class SendController extends WalletFormController implements Initializabl
}
}
private void addFeeRangeTrackHighlight(int count) {
Platform.runLater(() -> {
Node track = feeRange.lookup(".track");
if(track != null) {
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
String highlight = "";
if(targetBlocksFeeRates.get(Integer.MAX_VALUE) != null) {
highlight += "#a0a1a766 " + getPercentageOfFeeRange(targetBlocksFeeRates.get(Integer.MAX_VALUE)) + "%, ";
}
highlight += "#41a9c966 " + getPercentageOfFeeRange(targetBlocksFeeRates, FeeRatesSource.BLOCKS_IN_TWO_HOURS - 1) + "%, ";
highlight += "#fba71b66 " + getPercentageOfFeeRange(targetBlocksFeeRates, FeeRatesSource.BLOCKS_IN_HOUR - 1) + "%, ";
highlight += "#c8416466 " + getPercentageOfFeeRange(targetBlocksFeeRates, FeeRatesSource.BLOCKS_IN_HALF_HOUR - 1) + "%";
track.setStyle("-fx-background-color: " +
"-fx-shadow-highlight-color, " +
"linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border), " +
"linear-gradient(to bottom, derive(-fx-control-inner-background, -9%), derive(-fx-control-inner-background, 0%), derive(-fx-control-inner-background, -5%), derive(-fx-control-inner-background, -12%)), " +
"linear-gradient(to right, " + highlight + ")");
} else if(count < 20) {
addFeeRangeTrackHighlight(count+1);
}
});
}
private int getPercentageOfFeeRange(Map<Integer, Double> targetBlocksFeeRates, Integer minTargetBlocks) {
List<Integer> rates = new ArrayList<>(targetBlocksFeeRates.keySet());
Collections.reverse(rates);
for(Integer targetBlocks : rates) {
if(targetBlocks < minTargetBlocks) {
return getPercentageOfFeeRange(targetBlocksFeeRates.get(targetBlocks));
}
}
return 100;
}
private int getPercentageOfFeeRange(Double feeRate) {
double index = Math.log(feeRate) / Math.log(2);
return (int)Math.round(index * 10.0);
}
private void updateMaxClearButtons(UtxoSelector utxoSelector, UtxoFilter utxoFilter) {
if(utxoSelector instanceof PresetUtxoSelector) {
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
@ -811,6 +904,7 @@ public class SendController extends WalletFormController implements Initializabl
if(targetBlocksField.isVisible()) {
setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks()));
}
addFeeRangeTrackHighlight(0);
}
@Subscribe

View file

@ -91,6 +91,12 @@
</Field>
<Field fx:id="feeRateField" text="Rate:">
<CopyableLabel fx:id="feeRate" />
<Region HBox.hgrow="ALWAYS" />
<Label fx:id="feeRatePriority" graphicTextGap="5">
<graphic>
<Glyph fx:id="feeRatePriorityGlyph" fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="CIRCLE" />
</graphic>
</Label>
</Field>
<Field text="Fee:">
<TextField fx:id="fee" styleClass="amount-field"/>