mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
add fee rate priority indicator
This commit is contained in:
parent
13d701b0a7
commit
7ed75fc83d
5 changed files with 122 additions and 10 deletions
|
@ -1,5 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.sparrow.wallet.SendController;
|
||||||
import javafx.beans.NamedArg;
|
import javafx.beans.NamedArg;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.chart.Axis;
|
import javafx.scene.chart.Axis;
|
||||||
|
@ -27,10 +28,12 @@ public class BlockTargetFeeRatesChart extends LineChart<String, Number> {
|
||||||
|
|
||||||
for(Iterator<Integer> targetBlocksIter = targetBlocksFeeRates.keySet().iterator(); targetBlocksIter.hasNext(); ) {
|
for(Iterator<Integer> targetBlocksIter = targetBlocksFeeRates.keySet().iterator(); targetBlocksIter.hasNext(); ) {
|
||||||
Integer targetBlocks = targetBlocksIter.next();
|
Integer targetBlocks = targetBlocksIter.next();
|
||||||
|
if(SendController.TARGET_BLOCKS_RANGE.contains(targetBlocks)) {
|
||||||
String category = targetBlocks + (targetBlocksIter.hasNext() ? "" : "+");
|
String category = targetBlocks + (targetBlocksIter.hasNext() ? "" : "+");
|
||||||
XYChart.Data<String, Number> data = new XYChart.Data<>(category, targetBlocksFeeRates.get(targetBlocks));
|
XYChart.Data<String, Number> data = new XYChart.Data<>(category, targetBlocksFeeRates.get(targetBlocks));
|
||||||
feeRateSeries.getData().add(data);
|
feeRateSeries.getData().add(data);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(selectedTargetBlocks != null) {
|
if(selectedTargetBlocks != null) {
|
||||||
select(selectedTargetBlocks);
|
select(selectedTargetBlocks);
|
||||||
|
|
|
@ -40,6 +40,7 @@ public class FontAwesome5 extends GlyphFont {
|
||||||
LOCK_OPEN('\uf3c1'),
|
LOCK_OPEN('\uf3c1'),
|
||||||
PEN_FANCY('\uf5ac'),
|
PEN_FANCY('\uf5ac'),
|
||||||
PLUS('\uf067'),
|
PLUS('\uf067'),
|
||||||
|
PLUS_CIRCLE('\uf055'),
|
||||||
QRCODE('\uf029'),
|
QRCODE('\uf029'),
|
||||||
QUESTION_CIRCLE('\uf059'),
|
QUESTION_CIRCLE('\uf059'),
|
||||||
RANDOM('\uf074'),
|
RANDOM('\uf074'),
|
||||||
|
|
|
@ -40,6 +40,9 @@ public enum FeeRatesSource {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(FeeRatesSource.class);
|
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;
|
private final String name;
|
||||||
|
|
||||||
|
@ -61,16 +64,20 @@ public enum FeeRatesSource {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
ThreeTierRates threeTierRates = gson.fromJson(reader, ThreeTierRates.class);
|
ThreeTierRates threeTierRates = gson.fromJson(reader, ThreeTierRates.class);
|
||||||
for(Integer blockTarget : defaultblockTargetFeeRates.keySet()) {
|
for(Integer blockTarget : defaultblockTargetFeeRates.keySet()) {
|
||||||
if(blockTarget < 3) {
|
if(blockTarget < BLOCKS_IN_HALF_HOUR) {
|
||||||
blockTargetFeeRates.put(blockTarget, threeTierRates.fastestFee);
|
blockTargetFeeRates.put(blockTarget, threeTierRates.fastestFee);
|
||||||
} else if(blockTarget < 6) {
|
} else if(blockTarget < BLOCKS_IN_HOUR) {
|
||||||
blockTargetFeeRates.put(blockTarget, threeTierRates.halfHourFee);
|
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);
|
blockTargetFeeRates.put(blockTarget, threeTierRates.hourFee);
|
||||||
} else {
|
} else {
|
||||||
blockTargetFeeRates.put(blockTarget, defaultblockTargetFeeRates.get(blockTarget));
|
blockTargetFeeRates.put(blockTarget, defaultblockTargetFeeRates.get(blockTarget));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(threeTierRates.minimumFee != null) {
|
||||||
|
blockTargetFeeRates.put(Integer.MAX_VALUE, threeTierRates.minimumFee);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Error retrieving recommended fee rates from " + url, e);
|
log.warn("Error retrieving recommended fee rates from " + url, e);
|
||||||
}
|
}
|
||||||
|
@ -98,5 +105,6 @@ public enum FeeRatesSource {
|
||||||
Double fastestFee;
|
Double fastestFee;
|
||||||
Double halfHourFee;
|
Double halfHourFee;
|
||||||
Double hourFee;
|
Double hourFee;
|
||||||
|
Double minimumFee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,11 @@ import com.sparrowwallet.sparrow.CurrencyRate;
|
||||||
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.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||||
|
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
||||||
import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
@ -28,6 +30,7 @@ import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
import org.controlsfx.validation.ValidationResult;
|
import org.controlsfx.validation.ValidationResult;
|
||||||
import org.controlsfx.validation.ValidationSupport;
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
import org.controlsfx.validation.Validator;
|
import org.controlsfx.validation.Validator;
|
||||||
|
@ -79,6 +82,12 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
@FXML
|
@FXML
|
||||||
private CopyableLabel feeRate;
|
private CopyableLabel feeRate;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label feeRatePriority;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Glyph feeRatePriorityGlyph;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField fee;
|
private TextField fee;
|
||||||
|
|
||||||
|
@ -350,6 +359,8 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addFeeRangeTrackHighlight(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeTabHeader(int count) {
|
private void initializeTabHeader(int count) {
|
||||||
|
@ -558,12 +569,14 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
||||||
int maxTargetBlocks = 1;
|
int maxTargetBlocks = 1;
|
||||||
for(Integer targetBlocks : targetBlocksFeeRates.keySet()) {
|
for(Integer targetBlocks : targetBlocksFeeRates.keySet()) {
|
||||||
|
if(TARGET_BLOCKS_RANGE.contains(targetBlocks)) {
|
||||||
maxTargetBlocks = Math.max(maxTargetBlocks, targetBlocks);
|
maxTargetBlocks = Math.max(maxTargetBlocks, targetBlocks);
|
||||||
Double candidate = targetBlocksFeeRates.get(targetBlocks);
|
Double candidate = targetBlocksFeeRates.get(targetBlocks);
|
||||||
if(Math.round(feeRate) >= Math.round(candidate)) {
|
if(Math.round(feeRate) >= Math.round(candidate)) {
|
||||||
return targetBlocks;
|
return targetBlocks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return maxTargetBlocks;
|
return maxTargetBlocks;
|
||||||
}
|
}
|
||||||
|
@ -623,6 +636,45 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
private void setFeeRate(Double feeRateAmt) {
|
private void setFeeRate(Double feeRateAmt) {
|
||||||
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte");
|
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() {
|
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) {
|
private void updateMaxClearButtons(UtxoSelector utxoSelector, UtxoFilter utxoFilter) {
|
||||||
if(utxoSelector instanceof PresetUtxoSelector) {
|
if(utxoSelector instanceof PresetUtxoSelector) {
|
||||||
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
|
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
|
||||||
|
@ -811,6 +904,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
if(targetBlocksField.isVisible()) {
|
if(targetBlocksField.isVisible()) {
|
||||||
setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks()));
|
setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks()));
|
||||||
}
|
}
|
||||||
|
addFeeRangeTrackHighlight(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
@ -91,6 +91,12 @@
|
||||||
</Field>
|
</Field>
|
||||||
<Field fx:id="feeRateField" text="Rate:">
|
<Field fx:id="feeRateField" text="Rate:">
|
||||||
<CopyableLabel fx:id="feeRate" />
|
<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>
|
||||||
<Field text="Fee:">
|
<Field text="Fee:">
|
||||||
<TextField fx:id="fee" styleClass="amount-field"/>
|
<TextField fx:id="fee" styleClass="amount-field"/>
|
||||||
|
|
Loading…
Reference in a new issue