Merge branch 'low_fee' of https://github.com/ThauanAmorim/sparrow into low_fee

This commit is contained in:
Thauan Amorim 2025-07-16 07:19:59 -03:00
commit a82dda856d
4 changed files with 39 additions and 23 deletions

View file

@ -91,8 +91,8 @@ public class AppServices {
private static final String TOR_DEFAULT_PROXY_CIRCUIT_ID = "default";
public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50);
public static final List<Long> LONG_FEE_RATES_RANGE = List.of(1L, 2L, 4L, 8L, 16L, 32L, 64L, 128L, 256L, 512L, 1024L, 2048L, 4096L, 8192L);
public static final List<Long> FEE_RATES_RANGE = LONG_FEE_RATES_RANGE.subList(0, LONG_FEE_RATES_RANGE.size() - 3);
public static final List<Double> 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<Double> 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;

View file

@ -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);
}

View file

@ -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<ButtonType> 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) {

View file

@ -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<Integer, Double> 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();