diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 53df02ac..6b8d29ce 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -91,8 +91,8 @@ public class AppServices { private static final String TOR_DEFAULT_PROXY_CIRCUIT_ID = "default"; public static final List TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50); - public static final List LONG_FEE_RATES_RANGE = List.of(1L, 2L, 4L, 8L, 16L, 32L, 64L, 128L, 256L, 512L, 1024L, 2048L, 4096L, 8192L); - public static final List FEE_RATES_RANGE = LONG_FEE_RATES_RANGE.subList(0, LONG_FEE_RATES_RANGE.size() - 3); + public static final List DOUBLE_FEE_RATES_RANGE = List.of(0.01D, 0.05D, 0.1D, 0.5, 1D, 2D, 4D, 8D, 16D, 32D, 64D, 128D, 256D, 512D, 1024D, 2048D, 4096D, 8192D); + public static final List FEE_RATES_RANGE = DOUBLE_FEE_RATES_RANGE.subList(0, DOUBLE_FEE_RATES_RANGE.size() - 8); public static final double FALLBACK_FEE_RATE = 20000d / 1000; public static final double TESTNET_FALLBACK_FEE_RATE = 1000d / 1000; diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FeeRangeSlider.java b/src/main/java/com/sparrowwallet/sparrow/control/FeeRangeSlider.java index 335d9904..b79af065 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FeeRangeSlider.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FeeRangeSlider.java @@ -27,11 +27,11 @@ public class FeeRangeSlider extends Slider { setLabelFormatter(new StringConverter<>() { @Override public String toString(Double object) { - Long feeRate = LONG_FEE_RATES_RANGE.get(object.intValue()); - if(isLongFeeRange() && feeRate >= 1000) { + Double feeRate = DOUBLE_FEE_RATES_RANGE.get(object.intValue()); + if(isDoubleFeeRange() && feeRate >= 1000) { return feeRate / 1000 + "k"; } - return Long.toString(feeRate); + return feeRate < 1 ? Double.toString(feeRate) : String.format("%.0f", feeRate); } @Override @@ -51,10 +51,10 @@ public class FeeRangeSlider extends Slider { setOnScroll(event -> { if(event.getDeltaY() != 0) { double newFeeRate = getFeeRate() + (event.getDeltaY() > 0 ? FEE_RATE_SCROLL_INCREMENT : -FEE_RATE_SCROLL_INCREMENT); - if(newFeeRate < LONG_FEE_RATES_RANGE.get(0)) { - newFeeRate = LONG_FEE_RATES_RANGE.get(0); - } else if(newFeeRate > LONG_FEE_RATES_RANGE.get(LONG_FEE_RATES_RANGE.size() - 1)) { - newFeeRate = LONG_FEE_RATES_RANGE.get(LONG_FEE_RATES_RANGE.size() - 1); + if(newFeeRate < DOUBLE_FEE_RATES_RANGE.get(0)) { + newFeeRate = DOUBLE_FEE_RATES_RANGE.get(0); + } else if(newFeeRate > DOUBLE_FEE_RATES_RANGE.get(DOUBLE_FEE_RATES_RANGE.size() - 1)) { + newFeeRate = DOUBLE_FEE_RATES_RANGE.get(DOUBLE_FEE_RATES_RANGE.size() - 1); } setFeeRate(newFeeRate); } @@ -62,7 +62,15 @@ public class FeeRangeSlider extends Slider { } public double getFeeRate() { - return Math.pow(2.0, getValue()); + double value = getValue(); + // First range: 0.01, 0.05, 0.1 + if(value < 1) return 0.01 + (0.05 - 0.01) * value; + if(value < 2) return 0.05 + (0.1 - 0.05) * (value - 1); + // Second range: 0.1, 0.5, 1 + if(value < 3) return 0.1 + (0.5 - 0.1) * (value - 2); + if(value < 4) return 0.5 + (1.0 - 0.5) * (value - 3); + // Third range: 1, 2, 4, 8, ... + return Math.pow(2, value - 4 + 0) * 1.0; } public void setFeeRate(double feeRate) { @@ -72,16 +80,18 @@ public class FeeRangeSlider extends Slider { } private void updateMaxFeeRange(double value) { - if(value >= getMax() && !isLongFeeRange()) { - setMax(LONG_FEE_RATES_RANGE.size() - 1); + if(value >= getMax() && !isDoubleFeeRange()) { + setMin(FEE_RATES_RANGE.size() - 2); + setMax(DOUBLE_FEE_RATES_RANGE.size() - 1); updateTrackHighlight(); - } else if(value == getMin() && isLongFeeRange()) { + } else if(value == getMin() && isDoubleFeeRange()) { + setMin(0); setMax(FEE_RATES_RANGE.size() - 1); updateTrackHighlight(); } } - private boolean isLongFeeRange() { + private boolean isDoubleFeeRange() { return getMax() > FEE_RATES_RANGE.size() - 1; } @@ -138,8 +148,8 @@ public class FeeRangeSlider extends Slider { private int getPercentageOfFeeRange(Double feeRate) { double index = Math.log(feeRate) / Math.log(2); - if(isLongFeeRange()) { - index *= ((double)FEE_RATES_RANGE.size() / (LONG_FEE_RATES_RANGE.size())) * 0.99; + if(isDoubleFeeRange()) { + index *= ((double)FEE_RATES_RANGE.size() / (DOUBLE_FEE_RATES_RANGE.size())) * 0.99; } return (int)Math.round(index * 10.0); } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 4fd6b76e..d0125814 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -1139,7 +1139,7 @@ public class HeadersController extends TransactionFormController implements Init if(fee.getValue() > 0) { double feeRateAmt = fee.getValue() / headersForm.getTransaction().getVirtualSize(); - if(feeRateAmt > AppServices.LONG_FEE_RATES_RANGE.get(AppServices.LONG_FEE_RATES_RANGE.size() - 1)) { + if(feeRateAmt > AppServices.DOUBLE_FEE_RATES_RANGE.get(AppServices.DOUBLE_FEE_RATES_RANGE.size() - 1)) { Optional optType = AppServices.showWarningDialog("Very high fee rate!", "This transaction pays a very high fee rate of " + String.format("%.0f", feeRateAmt) + " sats/vB.\n\nBroadcast this transaction?", ButtonType.YES, ButtonType.NO); if(optType.isPresent() && optType.get() == ButtonType.NO) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index 57f4d970..bdcd9ad4 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -184,6 +184,7 @@ public class SendController extends WalletFormController implements Initializabl setFiatFeeAmount(AppServices.getFiatCurrencyExchangeRate(), getFeeValueSats()); } + createButton.setDisable(isInsufficientFeeRate()); setTargetBlocks(getTargetBlocks()); updateTransaction(); } @@ -484,8 +485,8 @@ public class SendController extends WalletFormController implements Initializabl validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); validationSupport.registerValidator(fee, Validator.combine( (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", userFeeSet.get() && insufficientInputsProperty.get()), - (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee", getFeeValueSats() != null && getFeeValueSats() == 0), - (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee Rate", isInsufficientFeeRate()) + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee", isInsufficientFeeRate()), + (Control c, String newValue) -> ValidationResult.fromWarningIf( c, "Fee Rate Below Minimum", isBelowMinimumFeeRate()) )); validationSupport.setErrorDecorationEnabled(false); @@ -903,10 +904,14 @@ public class SendController extends WalletFormController implements Initializabl return AppServices.getMempoolHistogram(); } - public boolean isInsufficientFeeRate() { + public boolean isBelowMinimumFeeRate() { return walletTransactionProperty.get() != null && walletTransactionProperty.get().getFeeRate() < AppServices.getMinimumRelayFeeRate(); } + public boolean isInsufficientFeeRate() { + return getFeeValueSats() == null || getFeeValueSats() == 0; + } + private void setFeeRate(Double feeRateAmt) { UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat(); feeRate.setText(format.getCurrencyFormat().format(feeRateAmt) + (cpfpFeeRate.isVisible() ? "" : " sats/vB")); @@ -941,13 +946,13 @@ public class SendController extends WalletFormController implements Initializabl } private void setFeeRatePriority(Double feeRateAmt) { + feeRateAmt = Math.round(feeRateAmt * 100.0) / 100.0; // Round to 2 decimal places Map targetBlocksFeeRates = getTargetBlocksFeeRates(); - Integer targetBlocks = getTargetBlocks(feeRateAmt); if(targetBlocksFeeRates.get(Integer.MAX_VALUE) != null) { Double minFeeRate = targetBlocksFeeRates.get(Integer.MAX_VALUE); - if(minFeeRate > 1.0 && feeRateAmt < minFeeRate) { + if(feeRateAmt > 0.01 && feeRateAmt < minFeeRate) { feeRatePriority.setText("Below Minimum"); - feeRatePriority.setTooltip(new Tooltip("Transactions at this fee rate are currently being purged from the default sized mempool")); + feeRatePriority.setTooltip(new Tooltip("Transactions at this fee rate can be purged from the default sized mempool")); feeRatePriorityGlyph.setStyle("-fx-text-fill: #a0a1a7cc"); feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.EXCLAMATION_CIRCLE); return; @@ -963,6 +968,7 @@ public class SendController extends WalletFormController implements Initializabl } } + Integer targetBlocks = getTargetBlocks(feeRateAmt); if(targetBlocks != null) { if(targetBlocks < FeeRatesSource.BLOCKS_IN_HALF_HOUR) { Double maxFeeRate = FEE_RATES_RANGE.get(FEE_RATES_RANGE.size() - 1).doubleValue();