diff --git a/drongo b/drongo index 49799fc0..6b20c655 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 49799fc0c8b5245a7931d0437a68172f9b6efbbc +Subproject commit 6b20c6558ab7cef6f582461692232a7687fe26c8 diff --git a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java index 4ba67b3d..3efbaf2d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java @@ -2,8 +2,11 @@ package com.sparrowwallet.sparrow.control; import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.address.Address; -import com.sparrowwallet.drongo.wallet.BlockTransaction; -import com.sparrowwallet.drongo.wallet.KeystoreSource; +import com.sparrowwallet.drongo.protocol.NonStandardScriptException; +import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.protocol.TransactionOutput; +import com.sparrowwallet.drongo.wallet.*; +import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; @@ -19,7 +22,7 @@ import org.controlsfx.glyphfont.Glyph; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; class EntryCell extends TreeTableCell { @@ -46,10 +49,10 @@ class EntryCell extends TreeTableCell { TransactionEntry transactionEntry = (TransactionEntry)entry; if(transactionEntry.getBlockTransaction().getHeight() == -1) { setText("Unconfirmed Parent"); - setContextMenu(new UnconfirmedTransactionContextMenu(transactionEntry.getBlockTransaction())); + setContextMenu(new UnconfirmedTransactionContextMenu(transactionEntry)); } else if(transactionEntry.getBlockTransaction().getHeight() == 0) { setText("Unconfirmed"); - setContextMenu(new UnconfirmedTransactionContextMenu(transactionEntry.getBlockTransaction())); + setContextMenu(new UnconfirmedTransactionContextMenu(transactionEntry)); } else { String date = DATE_FORMAT.format(transactionEntry.getBlockTransaction().getDate()); setText(date); @@ -60,6 +63,7 @@ class EntryCell extends TreeTableCell { tooltip.setText(transactionEntry.getBlockTransaction().getHash().toString()); setTooltip(tooltip); + HBox actionBox = new HBox(); Button viewTransactionButton = new Button(""); Glyph searchGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SEARCH); searchGlyph.setFontSize(12); @@ -67,7 +71,21 @@ class EntryCell extends TreeTableCell { viewTransactionButton.setOnAction(event -> { EventManager.get().post(new ViewTransactionEvent(transactionEntry.getBlockTransaction())); }); - setGraphic(viewTransactionButton); + actionBox.getChildren().add(viewTransactionButton); + + BlockTransaction blockTransaction = transactionEntry.getBlockTransaction(); + if(blockTransaction.getHeight() <= 0 && blockTransaction.getTransaction().isReplaceByFee() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) { + Button increaseFeeButton = new Button(""); + Glyph increaseFeeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING_MEDICAL); + increaseFeeGlyph.setFontSize(12); + increaseFeeButton.setGraphic(increaseFeeGlyph); + increaseFeeButton.setOnAction(event -> { + increaseFee(transactionEntry); + }); + actionBox.getChildren().add(increaseFeeButton); + } + + setGraphic(actionBox); } else if(entry instanceof NodeEntry) { NodeEntry nodeEntry = (NodeEntry)entry; Address address = nodeEntry.getAddress(); @@ -139,9 +157,9 @@ class EntryCell extends TreeTableCell { utxoEntries = List.of(hashIndexEntry); } - final List spendingUtxoEntries = utxoEntries; - EventManager.get().post(new SendActionEvent(utxoEntries)); - Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(spendingUtxoEntries))); + final List spendingUtxos = utxoEntries.stream().map(HashIndexEntry::getHashIndex).collect(Collectors.toList()); + EventManager.get().post(new SendActionEvent(hashIndexEntry.getWallet(), spendingUtxos)); + Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(hashIndexEntry.getWallet(), spendingUtxos))); }); actionBox.getChildren().add(spendUtxoButton); } @@ -151,8 +169,63 @@ class EntryCell extends TreeTableCell { } } + private static void increaseFee(TransactionEntry transactionEntry) { + BlockTransaction blockTransaction = transactionEntry.getBlockTransaction(); + Map walletTxos = transactionEntry.getWallet().getWalletTxos(); + List utxos = transactionEntry.getChildren().stream() + .filter(e -> e instanceof HashIndexEntry) + .map(e -> (HashIndexEntry)e) + .filter(e -> e.getType().equals(HashIndexEntry.Type.INPUT) && e.isSpendable()) + .map(e -> blockTransaction.getTransaction().getInputs().get((int)e.getHashIndex().getIndex())) + .map(txInput -> walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txInput.getOutpoint().getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()).findFirst().get()) + .collect(Collectors.toList()); + + List ourOutputs = transactionEntry.getChildren().stream() + .filter(e -> e instanceof HashIndexEntry) + .map(e -> (HashIndexEntry)e) + .filter(e -> e.getType().equals(HashIndexEntry.Type.OUTPUT)) + .map(e -> e.getBlockTransaction().getTransaction().getOutputs().get((int)e.getHashIndex().getIndex())) + .collect(Collectors.toList()); + + long changeTotal = ourOutputs.stream().mapToLong(TransactionOutput::getValue).sum(); + Transaction tx = blockTransaction.getTransaction(); + int vSize = tx.getVirtualSize(); + int inputSize = tx.getInputs().get(0).getLength() + (tx.getInputs().get(0).hasWitness() ? tx.getInputs().get(0).getWitness().getLength() / Transaction.WITNESS_SCALE_FACTOR : 0); + List walletUtxos = new ArrayList<>(transactionEntry.getWallet().getWalletUtxos().keySet()); + Collections.shuffle(walletUtxos); + while((double)changeTotal / vSize < getMaxFeeRate() && !walletUtxos.isEmpty()) { + //If there is insufficent change output, include another random UTXO so the fee can be increased + BlockTransactionHashIndex utxo = walletUtxos.remove(0); + utxos.add(utxo); + changeTotal += utxo.getValue(); + vSize += inputSize; + } + + List externalOutputs = new ArrayList<>(blockTransaction.getTransaction().getOutputs()); + externalOutputs.removeAll(ourOutputs); + List payments = externalOutputs.stream().map(txOutput -> { + try { + return new Payment(txOutput.getScript().getToAddresses()[0], transactionEntry.getLabel(), txOutput.getValue(), false); + } catch(Exception e) { + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), utxos)); + Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, payments, blockTransaction.getFee(), true))); + } + + private static Double getMaxFeeRate() { + if(AppController.getTargetBlockFeeRates().isEmpty()) { + return 100.0; + } + + return AppController.getTargetBlockFeeRates().values().iterator().next(); + } + private static class UnconfirmedTransactionContextMenu extends ContextMenu { - public UnconfirmedTransactionContextMenu(BlockTransaction blockTransaction) { + public UnconfirmedTransactionContextMenu(TransactionEntry transactionEntry) { + BlockTransaction blockTransaction = transactionEntry.getBlockTransaction(); MenuItem copyTxid = new MenuItem("Copy Transaction ID"); copyTxid.setOnAction(AE -> { hide(); @@ -161,7 +234,17 @@ class EntryCell extends TreeTableCell { Clipboard.getSystemClipboard().setContent(content); }); - getItems().addAll(copyTxid); + getItems().add(copyTxid); + + if(blockTransaction.getTransaction().isReplaceByFee() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) { + MenuItem increaseFee = new MenuItem("Increase Fee"); + increaseFee.setOnAction(AE -> { + hide(); + increaseFee(transactionEntry); + }); + + getItems().add(increaseFee); + } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MempoolSizeFeeRatesChart.java b/src/main/java/com/sparrowwallet/sparrow/control/MempoolSizeFeeRatesChart.java index 4e9ddd32..6eb03b70 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MempoolSizeFeeRatesChart.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MempoolSizeFeeRatesChart.java @@ -174,7 +174,9 @@ public class MempoolSizeFeeRatesChart extends StackedAreaChart { if(mvb >= 0.01) { Label label = new Label(series.getName() + ": " + String.format("%.2f", mvb) + " MvB"); Glyph circle = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CIRCLE); - circle.setStyle("-fx-text-fill: CHART_COLOR_" + (i+1)); + if(i < 8) { + circle.setStyle("-fx-text-fill: CHART_COLOR_" + (i+1)); + } label.setGraphic(circle); getChildren().add(label); } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/SendActionEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/SendActionEvent.java index 092cce9d..486dc747 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/SendActionEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/SendActionEvent.java @@ -1,17 +1,24 @@ package com.sparrowwallet.sparrow.event; -import com.sparrowwallet.sparrow.wallet.HashIndexEntry; +import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; +import com.sparrowwallet.drongo.wallet.Wallet; import java.util.List; public class SendActionEvent { - private final List utxoEntries; + private final Wallet wallet; + private final List utxos; - public SendActionEvent(List utxoEntries) { - this.utxoEntries = utxoEntries; + public SendActionEvent(Wallet wallet, List utxos) { + this.wallet = wallet; + this.utxos = utxos; } - public List getUtxoEntries() { - return utxoEntries; + public Wallet getWallet() { + return wallet; + } + + public List getUtxos() { + return utxos; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/SpendUtxoEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/SpendUtxoEvent.java index 26a406d9..5a3944ad 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/SpendUtxoEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/SpendUtxoEvent.java @@ -1,17 +1,47 @@ package com.sparrowwallet.sparrow.event; -import com.sparrowwallet.sparrow.wallet.HashIndexEntry; +import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; +import com.sparrowwallet.drongo.wallet.Payment; +import com.sparrowwallet.drongo.wallet.Wallet; import java.util.List; public class SpendUtxoEvent { - private final List utxoEntries; + private final Wallet wallet; + private final List utxos; + private final List payments; + private final Long fee; + private final boolean includeMempoolInputs; - public SpendUtxoEvent(List utxoEntries) { - this.utxoEntries = utxoEntries; + public SpendUtxoEvent(Wallet wallet, List utxos) { + this(wallet, utxos, null, null, false); } - public List getUtxoEntries() { - return utxoEntries; + public SpendUtxoEvent(Wallet wallet, List utxos, List payments, Long fee, boolean includeMempoolInputs) { + this.wallet = wallet; + this.utxos = utxos; + this.payments = payments; + this.fee = fee; + this.includeMempoolInputs = includeMempoolInputs; + } + + public Wallet getWallet() { + return wallet; + } + + public List getUtxos() { + return utxos; + } + + public List getPayments() { + return payments; + } + + public Long getFee() { + return fee; + } + + public boolean isIncludeMempoolInputs() { + return includeMempoolInputs; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index 94b81c02..6e693dcf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -30,6 +30,7 @@ public class FontAwesome5 extends GlyphFont { ELLIPSIS_H('\uf141'), EYE('\uf06e'), HAND_HOLDING('\uf4bd'), + HAND_HOLDING_MEDICAL('\ue05c'), KEY('\uf084'), LAPTOP('\uf109'), LOCK('\uf023'), diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 1f91a015..081b4c91 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -138,7 +138,16 @@ public class ElectrumServer { public Map> getHistory(Wallet wallet, Collection nodes) throws ServerException { Map> nodeTransactionMap = new TreeMap<>(); - subscribeWalletNodes(wallet, nodes, nodeTransactionMap, 0); + + Set historyNodes = new HashSet<>(nodes); + //Add any nodes with mempool transactions in case these have been replaced + Set mempoolNodes = wallet.getWalletTxos().entrySet().stream() + .filter(entry -> entry.getKey().getHeight() <= 0 || (entry.getKey().getSpentBy() != null && entry.getKey().getSpentBy().getHeight() <= 0)) + .map(Map.Entry::getValue) + .collect(Collectors.toSet()); + historyNodes.addAll(mempoolNodes); + + subscribeWalletNodes(wallet, historyNodes, nodeTransactionMap, 0); getReferences(wallet, nodeTransactionMap.keySet(), nodeTransactionMap, 0); Set newReferences = nodeTransactionMap.values().stream().flatMap(Collection::stream).filter(ref -> !wallet.getTransactions().containsKey(ref.getHash())).collect(Collectors.toSet()); getReferencedTransactions(wallet, nodeTransactionMap); @@ -152,7 +161,7 @@ public class ElectrumServer { BlockTransaction blockTransaction = wallet.getTransactions().get(reference.getHash()); for(TransactionOutput txOutput : blockTransaction.getTransaction().getOutputs()) { WalletNode node = walletScriptHashes.get(getScriptHash(txOutput)); - if(node != null && !nodes.contains(node)) { + if(node != null && !historyNodes.contains(node)) { additionalNodes.add(node); } } @@ -162,7 +171,7 @@ public class ElectrumServer { if(inputBlockTransaction != null) { TransactionOutput txOutput = inputBlockTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex()); WalletNode node = walletScriptHashes.get(getScriptHash(txOutput)); - if(node != null && !nodes.contains(node)) { + if(node != null && !historyNodes.contains(node)) { additionalNodes.add(node); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/SubscriptionService.java b/src/main/java/com/sparrowwallet/sparrow/net/SubscriptionService.java index 1d90bce6..759ee3c2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/SubscriptionService.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/SubscriptionService.java @@ -1,6 +1,7 @@ package com.sparrowwallet.sparrow.net; import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; +import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional; import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; import com.google.common.collect.Iterables; @@ -23,7 +24,12 @@ public class SubscriptionService { } @JsonRpcMethod("blockchain.scripthash.subscribe") - public void scriptHashStatusUpdated(@JsonRpcParam("scripthash") final String scriptHash, @JsonRpcParam("status") final String status) { + public void scriptHashStatusUpdated(@JsonRpcParam("scripthash") final String scriptHash, @JsonRpcOptional @JsonRpcParam("status") final String status) { + if(status == null) { + //Mempool transaction was replaced returning change/consolidation script hash status to null, ignore this update + return; + } + Set existingStatuses = ElectrumServer.getSubscribedScriptHashes().get(scriptHash); if(existingStatuses == null) { log.warn("Received script hash status update for unsubscribed script hash: " + scriptHash); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java index 08f3a9d1..8616f8c0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java @@ -255,6 +255,10 @@ public class PaymentController extends WalletFormController implements Initializ public void setPayment(Payment payment) { if(getRecipientValueSats() == null || payment.getAmount() != getRecipientValueSats()) { + address.setText(payment.getAddress().toString()); + if(payment.getLabel() != null) { + label.setText(payment.getLabel()); + } setRecipientValueSats(payment.getAmount()); setFiatAmount(AppController.getFiatCurrencyExchangeRate(), payment.getAmount()); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index 00b18e69..4bcb153e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -119,6 +119,8 @@ public class SendController extends WalletFormController implements Initializabl private final StringProperty utxoLabelSelectionProperty = new SimpleStringProperty(""); + private final BooleanProperty includeMempoolInputsProperty = new SimpleBooleanProperty(false); + private final ChangeListener feeListener = new ChangeListener<>() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { @@ -147,7 +149,7 @@ public class SendController extends WalletFormController implements Initializabl feeRate.setText("Unknown"); } - Tooltip tooltip = new Tooltip("Target confirmation within " + target + " blocks"); + Tooltip tooltip = new Tooltip("Target inclusion within " + target + " blocks"); targetBlocks.setTooltip(tooltip); userFeeSet.set(false); @@ -271,6 +273,7 @@ public class SendController extends WalletFormController implements Initializabl FeeRatesSelection feeRatesSelection = Config.get().getFeeRatesSelection(); feeRatesSelection = (feeRatesSelection == null ? FeeRatesSelection.BLOCK_TARGET : feeRatesSelection); + setDefaultFeeRate(); updateFeeRateSelection(feeRatesSelection); feeSelectionToggleGroup.selectToggle(feeRatesSelection == FeeRatesSelection.BLOCK_TARGET ? targetBlocksToggle : mempoolSizeToggle); feeSelectionToggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { @@ -318,15 +321,12 @@ public class SendController extends WalletFormController implements Initializabl walletTransactionProperty.addListener((observable, oldValue, walletTransaction) -> { if(walletTransaction != null) { - for(int i = 0; i < paymentTabs.getTabs().size(); i++) { - Payment payment = walletTransaction.getPayments().get(i); - PaymentController controller = (PaymentController)paymentTabs.getTabs().get(i).getUserData(); - controller.setPayment(payment); - } + setPayments(walletTransaction.getPayments()); double feeRate = walletTransaction.getFeeRate(); if(userFeeSet.get()) { setTargetBlocks(getTargetBlocks(feeRate)); + setFeeRangeRate(feeRate); } else { setFeeValueSats(walletTransaction.getFee()); } @@ -386,7 +386,8 @@ public class SendController extends WalletFormController implements Initializabl } public Tab getPaymentTab() { - Tab tab = new Tab(Integer.toString(paymentTabs.getTabs().size() + 1)); + OptionalInt highestTabNo = paymentTabs.getTabs().stream().mapToInt(tab -> Integer.parseInt(tab.getText())).max(); + Tab tab = new Tab(Integer.toString(highestTabNo.isPresent() ? highestTabNo.getAsInt() + 1 : 1)); try { FXMLLoader paymentLoader = new FXMLLoader(AppController.class.getResource("wallet/payment.fxml")); @@ -411,6 +412,22 @@ public class SendController extends WalletFormController implements Initializabl return payments; } + public void setPayments(List payments) { + while(paymentTabs.getTabs().size() < payments.size()) { + addPaymentTab(); + } + + while(paymentTabs.getTabs().size() > payments.size()) { + paymentTabs.getTabs().remove(paymentTabs.getTabs().size() - 1); + } + + for(int i = 0; i < paymentTabs.getTabs().size(); i++) { + Payment payment = payments.get(i); + PaymentController controller = (PaymentController)paymentTabs.getTabs().get(i).getUserData(); + controller.setPayment(payment); + } + } + public void updateTransaction() { updateTransaction(null); } @@ -437,7 +454,8 @@ public class SendController extends WalletFormController implements Initializabl Integer currentBlockHeight = AppController.getCurrentBlockHeight(); boolean groupByAddress = Config.get().isGroupByAddress(); boolean includeMempoolChange = Config.get().isIncludeMempoolChange(); - WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), getUtxoFilters(), payments, getFeeRate(), getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolChange); + boolean includeMempoolInputs = includeMempoolInputsProperty.get(); + WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), getUtxoFilters(), payments, getFeeRate(), getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolChange, includeMempoolInputs); walletTransactionProperty.setValue(walletTransaction); insufficientInputsProperty.set(false); @@ -477,7 +495,11 @@ public class SendController extends WalletFormController implements Initializabl boolean blockTargetSelection = (feeRatesSelection == FeeRatesSelection.BLOCK_TARGET); targetBlocksField.setVisible(blockTargetSelection); blockTargetFeeRatesChart.setVisible(blockTargetSelection); - setDefaultFeeRate(); + if(blockTargetSelection) { + setTargetBlocks(getTargetBlocks(getFeeRangeRate())); + } else { + setFeeRangeRate(getTargetBlocksFeeRates().get(getTargetBlocks())); + } updateTransaction(); } @@ -485,14 +507,10 @@ public class SendController extends WalletFormController implements Initializabl int defaultTarget = TARGET_BLOCKS_RANGE.get((TARGET_BLOCKS_RANGE.size() / 2) - 1); int index = TARGET_BLOCKS_RANGE.indexOf(defaultTarget); Double defaultRate = getTargetBlocksFeeRates().get(defaultTarget); - if(targetBlocksField.isVisible()) { - targetBlocks.setValue(index); - blockTargetFeeRatesChart.select(defaultTarget); - setFeeRate(defaultRate); - } else { - feeRange.setValue(Math.log(defaultRate) / Math.log(2)); - setFeeRate(getFeeRangeRate()); - } + targetBlocks.setValue(index); + blockTargetFeeRatesChart.select(defaultTarget); + setFeeRangeRate(defaultRate); + setFeeRate(getFeeRangeRate()); } private Long getFeeValueSats() { @@ -528,7 +546,7 @@ public class SendController extends WalletFormController implements Initializabl for(Integer targetBlocks : targetBlocksFeeRates.keySet()) { maxTargetBlocks = Math.max(maxTargetBlocks, targetBlocks); Double candidate = targetBlocksFeeRates.get(targetBlocks); - if(feeRate > candidate) { + if(Math.round(feeRate) >= Math.round(candidate)) { return targetBlocks; } } @@ -541,6 +559,8 @@ public class SendController extends WalletFormController implements Initializabl int index = TARGET_BLOCKS_RANGE.indexOf(target); targetBlocks.setValue(index); blockTargetFeeRatesChart.select(target); + Tooltip tooltip = new Tooltip("Target inclusion within " + target + " blocks"); + targetBlocks.setTooltip(tooltip); targetBlocks.valueProperty().addListener(targetBlocksListener); } @@ -559,6 +579,12 @@ public class SendController extends WalletFormController implements Initializabl return Math.pow(2.0, feeRange.getValue()); } + private void setFeeRangeRate(Double feeRate) { + feeRange.valueProperty().removeListener(feeRangeListener); + feeRange.setValue(Math.log(feeRate) / Math.log(2)); + feeRange.valueProperty().addListener(feeRangeListener); + } + public Double getFeeRate() { if(targetBlocksField.isVisible()) { return getTargetBlocksFeeRates().get(getTargetBlocks()); @@ -631,9 +657,10 @@ public class SendController extends WalletFormController implements Initializabl fiatFeeAmount.setText(""); userFeeSet.set(false); - targetBlocks.setValue(4); + setDefaultFeeRate(); utxoSelectorProperty.setValue(null); utxoFilterProperty.setValue(null); + includeMempoolInputsProperty.set(false); walletTransactionProperty.setValue(null); createdWalletTransactionProperty.set(null); @@ -779,11 +806,23 @@ public class SendController extends WalletFormController implements Initializabl @Subscribe public void spendUtxos(SpendUtxoEvent event) { - if(!event.getUtxoEntries().isEmpty() && event.getUtxoEntries().get(0).getWallet().equals(getWalletForm().getWallet())) { - List utxos = event.getUtxoEntries().stream().map(HashIndexEntry::getHashIndex).collect(Collectors.toList()); + if(!event.getUtxos().isEmpty() && event.getWallet().equals(getWalletForm().getWallet())) { + if(event.getPayments() != null) { + clear(null); + setPayments(event.getPayments()); + } + + if(event.getFee() != null) { + setFeeValueSats(event.getFee()); + userFeeSet.set(true); + } + + includeMempoolInputsProperty.set(event.isIncludeMempoolInputs()); + + List utxos = event.getUtxos(); utxoSelectorProperty.set(new PresetUtxoSelector(utxos)); utxoFilterProperty.set(null); - updateTransaction(true); + updateTransaction(event.getPayments() == null); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java index fdf43c12..ece8e2e5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java @@ -138,9 +138,7 @@ public class TransactionEntry extends Entry implements Comparable e.getType().equals(HashIndexEntry.Type.OUTPUT) && e.isSpendable()) .collect(Collectors.toList()); - EventManager.get().post(new SendActionEvent(utxoEntries)); - Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(utxoEntries))); + final List spendingUtxos = utxoEntries.stream().map(HashIndexEntry::getHashIndex).collect(Collectors.toList()); + EventManager.get().post(new SendActionEvent(getWalletForm().getWallet(), spendingUtxos)); + Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(getWalletForm().getWallet(), spendingUtxos))); } public void clear(ActionEvent event) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java index 0bd97adb..bec99fd2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java @@ -121,7 +121,7 @@ public class WalletController extends WalletFormController implements Initializa @Subscribe public void sendAction(SendActionEvent event) { - if(!event.getUtxoEntries().isEmpty() && event.getUtxoEntries().get(0).getWallet().equals(walletForm.getWallet())) { + if(!event.getUtxos().isEmpty() && event.getWallet().equals(walletForm.getWallet())) { selectFunction(Function.SEND); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java index c6f19af2..31019793 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java @@ -76,7 +76,8 @@ public class WalletTransactionsEntry extends Entry { entriesAdded.removeAll(entriesComplete); for(Entry entry : entriesAdded) { TransactionEntry txEntry = (TransactionEntry)entry; - log.warn("Not notifying for incomplete entry " + ((TransactionEntry)entry).getBlockTransaction().getHashAsString() + " value " + txEntry.getValue()); + getChildren().remove(txEntry); + log.warn("Removing and not notifying incomplete entry " + ((TransactionEntry)entry).getBlockTransaction().getHashAsString() + " value " + txEntry.getValue()); } } } diff --git a/src/main/resources/font/fa-solid-900.ttf b/src/main/resources/font/fa-solid-900.ttf index 5b979039..e0746084 100644 Binary files a/src/main/resources/font/fa-solid-900.ttf and b/src/main/resources/font/fa-solid-900.ttf differ