mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-04 21:36:45 +00:00
add fee rate slider to private key sweep dialog
This commit is contained in:
parent
910a400b18
commit
30408af719
4 changed files with 149 additions and 69 deletions
|
@ -0,0 +1,102 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.AppServices.*;
|
||||||
|
|
||||||
|
public class FeeRangeSlider extends Slider {
|
||||||
|
public FeeRangeSlider() {
|
||||||
|
super(0, FEE_RATES_RANGE.size() - 1, 0);
|
||||||
|
setMajorTickUnit(1);
|
||||||
|
setMinorTickCount(0);
|
||||||
|
setSnapToTicks(false);
|
||||||
|
setShowTickLabels(true);
|
||||||
|
setShowTickMarks(true);
|
||||||
|
|
||||||
|
setLabelFormatter(new StringConverter<Double>() {
|
||||||
|
@Override
|
||||||
|
public String toString(Double object) {
|
||||||
|
return Long.toString(FEE_RATES_RANGE.get(object.intValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double fromString(String string) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateTrackHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFeeRate() {
|
||||||
|
return Math.pow(2.0, getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFeeRate(double feeRate) {
|
||||||
|
setValue(Math.log(feeRate) / Math.log(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTrackHighlight() {
|
||||||
|
addFeeRangeTrackHighlight(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFeeRangeTrackHighlight(int count) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Node track = 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 Map<Integer, Double> getTargetBlocksFeeRates() {
|
||||||
|
Map<Integer, Double> retrievedFeeRates = AppServices.getTargetBlockFeeRates();
|
||||||
|
if(retrievedFeeRates == null) {
|
||||||
|
retrievedFeeRates = TARGET_BLOCKS_RANGE.stream().collect(Collectors.toMap(java.util.function.Function.identity(), v -> FALLBACK_FEE_RATE,
|
||||||
|
(u, v) -> { throw new IllegalStateException("Duplicate target blocks"); },
|
||||||
|
LinkedHashMap::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retrievedFeeRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
|
@ -14,6 +15,9 @@ import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
|
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.io.CardApi;
|
import com.sparrowwallet.sparrow.io.CardApi;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
|
@ -46,10 +50,7 @@ import tornadofx.control.Form;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.sparrowwallet.drongo.protocol.ScriptType.P2TR;
|
import static com.sparrowwallet.drongo.protocol.ScriptType.P2TR;
|
||||||
|
@ -62,6 +63,8 @@ public class PrivateKeySweepDialog extends Dialog<Transaction> {
|
||||||
private final CopyableLabel keyAddress;
|
private final CopyableLabel keyAddress;
|
||||||
private final ComboBoxTextField toAddress;
|
private final ComboBoxTextField toAddress;
|
||||||
private final ComboBox<Wallet> toWallet;
|
private final ComboBox<Wallet> toWallet;
|
||||||
|
private final FeeRangeSlider feeRange;
|
||||||
|
private final CopyableLabel feeRate;
|
||||||
|
|
||||||
public PrivateKeySweepDialog(Wallet wallet) {
|
public PrivateKeySweepDialog(Wallet wallet) {
|
||||||
final DialogPane dialogPane = getDialogPane();
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
@ -146,7 +149,24 @@ public class PrivateKeySweepDialog extends Dialog<Transaction> {
|
||||||
stackPane.getChildren().addAll(toWallet, toAddress);
|
stackPane.getChildren().addAll(toWallet, toAddress);
|
||||||
toAddressField.getInputs().add(stackPane);
|
toAddressField.getInputs().add(stackPane);
|
||||||
|
|
||||||
fieldset.getChildren().addAll(keyField, keyScriptTypeField, addressField, toAddressField);
|
Field feeRangeField = new Field();
|
||||||
|
feeRangeField.setText("Fee range:");
|
||||||
|
feeRange = new FeeRangeSlider();
|
||||||
|
feeRange.setMaxWidth(320);
|
||||||
|
feeRangeField.getInputs().add(feeRange);
|
||||||
|
|
||||||
|
Field feeRateField = new Field();
|
||||||
|
feeRateField.setText("Fee rate:");
|
||||||
|
feeRate = new CopyableLabel();
|
||||||
|
feeRateField.getInputs().add(feeRate);
|
||||||
|
|
||||||
|
feeRange.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
updateFeeRate();
|
||||||
|
});
|
||||||
|
feeRange.setFeeRate(AppServices.getDefaultFeeRate());
|
||||||
|
updateFeeRate();
|
||||||
|
|
||||||
|
fieldset.getChildren().addAll(keyField, keyScriptTypeField, addressField, toAddressField, feeRangeField, feeRateField);
|
||||||
form.getChildren().add(fieldset);
|
form.getChildren().add(fieldset);
|
||||||
dialogPane.setContent(form);
|
dialogPane.setContent(form);
|
||||||
|
|
||||||
|
@ -201,6 +221,11 @@ public class PrivateKeySweepDialog extends Dialog<Transaction> {
|
||||||
setResultConverter(dialogButton -> null);
|
setResultConverter(dialogButton -> null);
|
||||||
dialogPane.setPrefWidth(680);
|
dialogPane.setPrefWidth(680);
|
||||||
|
|
||||||
|
EventManager.get().register(this);
|
||||||
|
setOnCloseRequest(event -> {
|
||||||
|
EventManager.get().unregister(this);
|
||||||
|
});
|
||||||
|
|
||||||
ValidationSupport validationSupport = new ValidationSupport();
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
|
@ -357,7 +382,7 @@ public class PrivateKeySweepDialog extends Dialog<Transaction> {
|
||||||
TransactionOutput sweepOutput = new TransactionOutput(noFeeTransaction, total, destAddress.getOutputScript());
|
TransactionOutput sweepOutput = new TransactionOutput(noFeeTransaction, total, destAddress.getOutputScript());
|
||||||
noFeeTransaction.addOutput(sweepOutput);
|
noFeeTransaction.addOutput(sweepOutput);
|
||||||
|
|
||||||
Double feeRate = AppServices.getDefaultFeeRate();
|
double feeRate = feeRange.getFeeRate();
|
||||||
long fee = (long)Math.ceil(noFeeTransaction.getVirtualSize() * feeRate);
|
long fee = (long)Math.ceil(noFeeTransaction.getVirtualSize() * feeRate);
|
||||||
if(feeRate == Transaction.DEFAULT_MIN_RELAY_FEE) {
|
if(feeRate == Transaction.DEFAULT_MIN_RELAY_FEE) {
|
||||||
fee++;
|
fee++;
|
||||||
|
@ -425,6 +450,16 @@ public class PrivateKeySweepDialog extends Dialog<Transaction> {
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateFeeRate() {
|
||||||
|
UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat();
|
||||||
|
feeRate.setText(format.getCurrencyFormat().format(feeRange.getFeeRate()) + " sats/vB");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void feeRatesUpdated(FeeRatesUpdatedEvent event) {
|
||||||
|
feeRange.updateTrackHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
public class PassphraseDialog extends Dialog<String> {
|
public class PassphraseDialog extends Dialog<String> {
|
||||||
private final CustomPasswordField passphrase;
|
private final CustomPasswordField passphrase;
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
private Field feeRangeField;
|
private Field feeRangeField;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Slider feeRange;
|
private FeeRangeSlider feeRange;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CopyableLabel feeRate;
|
private CopyableLabel feeRate;
|
||||||
|
@ -306,21 +306,6 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
feeRangeField.managedProperty().bind(feeRangeField.visibleProperty());
|
feeRangeField.managedProperty().bind(feeRangeField.visibleProperty());
|
||||||
feeRangeField.visibleProperty().bind(targetBlocksField.visibleProperty().not());
|
feeRangeField.visibleProperty().bind(targetBlocksField.visibleProperty().not());
|
||||||
feeRange.setMin(0);
|
|
||||||
feeRange.setMax(FEE_RATES_RANGE.size() - 1);
|
|
||||||
feeRange.setMajorTickUnit(1);
|
|
||||||
feeRange.setMinorTickCount(0);
|
|
||||||
feeRange.setLabelFormatter(new StringConverter<Double>() {
|
|
||||||
@Override
|
|
||||||
public String toString(Double object) {
|
|
||||||
return Long.toString(FEE_RATES_RANGE.get(object.intValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double fromString(String string) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
feeRange.valueProperty().addListener(feeRangeListener);
|
feeRange.valueProperty().addListener(feeRangeListener);
|
||||||
|
|
||||||
blockTargetFeeRatesChart.managedProperty().bind(blockTargetFeeRatesChart.visibleProperty());
|
blockTargetFeeRatesChart.managedProperty().bind(blockTargetFeeRatesChart.visibleProperty());
|
||||||
|
@ -424,8 +409,6 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addFeeRangeTrackHighlight(0);
|
|
||||||
|
|
||||||
efficiencyToggle.setOnAction(event -> {
|
efficiencyToggle.setOnAction(event -> {
|
||||||
if(StandardAccount.WHIRLPOOL_MIX_ACCOUNTS.contains(getWalletForm().getWallet().getStandardAccountType()) && !overrideOptimizationStrategy) {
|
if(StandardAccount.WHIRLPOOL_MIX_ACCOUNTS.contains(getWalletForm().getWallet().getStandardAccountType()) && !overrideOptimizationStrategy) {
|
||||||
AppServices.showWarningDialog("Privacy may be lost!", "It is recommended to optimize for privacy when sending coinjoined outputs.");
|
AppServices.showWarningDialog("Privacy may be lost!", "It is recommended to optimize for privacy when sending coinjoined outputs.");
|
||||||
|
@ -846,12 +829,12 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
}
|
}
|
||||||
|
|
||||||
private Double getFeeRangeRate() {
|
private Double getFeeRangeRate() {
|
||||||
return Math.pow(2.0, feeRange.getValue());
|
return feeRange.getFeeRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFeeRangeRate(Double feeRate) {
|
private void setFeeRangeRate(Double feeRate) {
|
||||||
feeRange.valueProperty().removeListener(feeRangeListener);
|
feeRange.valueProperty().removeListener(feeRangeListener);
|
||||||
feeRange.setValue(Math.log(feeRate) / Math.log(2));
|
feeRange.setFeeRate(feeRate);
|
||||||
feeRange.valueProperty().addListener(feeRangeListener);
|
feeRange.valueProperty().addListener(feeRangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -981,47 +964,6 @@ 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, TxoFilter txoFilter) {
|
private void updateMaxClearButtons(UtxoSelector utxoSelector, TxoFilter txoFilter) {
|
||||||
if(utxoSelector instanceof PresetUtxoSelector presetUtxoSelector) {
|
if(utxoSelector instanceof PresetUtxoSelector presetUtxoSelector) {
|
||||||
int num = presetUtxoSelector.getPresetUtxos().size();
|
int num = presetUtxoSelector.getPresetUtxos().size();
|
||||||
|
@ -1507,7 +1449,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
} else {
|
} else {
|
||||||
setFeeRatePriority(getFeeRangeRate());
|
setFeeRatePriority(getFeeRangeRate());
|
||||||
}
|
}
|
||||||
addFeeRangeTrackHighlight(0);
|
feeRange.updateTrackHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
<?import com.sparrowwallet.sparrow.wallet.FeeRatesSelection?>
|
<?import com.sparrowwallet.sparrow.wallet.FeeRatesSelection?>
|
||||||
<?import com.sparrowwallet.sparrow.wallet.OptimizationStrategy?>
|
<?import com.sparrowwallet.sparrow.wallet.OptimizationStrategy?>
|
||||||
<?import com.sparrowwallet.sparrow.control.HelpLabel?>
|
<?import com.sparrowwallet.sparrow.control.HelpLabel?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.FeeRangeSlider?>
|
||||||
|
|
||||||
<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">
|
<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>
|
<center>
|
||||||
|
@ -88,7 +89,7 @@
|
||||||
<Slider fx:id="targetBlocks" snapToTicks="true" showTickLabels="true" showTickMarks="true" />
|
<Slider fx:id="targetBlocks" snapToTicks="true" showTickLabels="true" showTickMarks="true" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field fx:id="feeRangeField" text="Range:">
|
<Field fx:id="feeRangeField" text="Range:">
|
||||||
<Slider fx:id="feeRange" snapToTicks="false" showTickLabels="true" showTickMarks="true" />
|
<FeeRangeSlider fx:id="feeRange" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field fx:id="feeRateField" text="Rate:">
|
<Field fx:id="feeRateField" text="Rate:">
|
||||||
<CopyableLabel fx:id="feeRate" />
|
<CopyableLabel fx:id="feeRate" />
|
||||||
|
|
Loading…
Reference in a new issue