diff --git a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java index 5522451f..f7d0a62d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java @@ -101,7 +101,7 @@ public class EntryCell extends TreeTableCell { Button increaseFeeButton = new Button(""); increaseFeeButton.setGraphic(getIncreaseFeeRBFGlyph()); increaseFeeButton.setOnAction(event -> { - increaseFee(transactionEntry); + increaseFee(transactionEntry, false); }); actionBox.getChildren().add(increaseFeeButton); } @@ -189,7 +189,7 @@ public class EntryCell extends TreeTableCell { } } - private static void increaseFee(TransactionEntry transactionEntry) { + private static void increaseFee(TransactionEntry transactionEntry, boolean cancelTransaction) { BlockTransaction blockTransaction = transactionEntry.getBlockTransaction(); Map walletTxos = transactionEntry.getWallet().getWalletTxos(); List utxos = transactionEntry.getChildren().stream() @@ -229,7 +229,7 @@ public class EntryCell extends TreeTableCell { //Remove any UTXOs created by the transaction that is to be replaced walletUtxos.removeIf(utxo -> ourOutputs.stream().anyMatch(output -> output.getHash().equals(utxo.getHash()) && output.getIndex() == utxo.getIndex())); Collections.shuffle(walletUtxos); - while((double)changeTotal / vSize < getMaxFeeRate() && !walletUtxos.isEmpty()) { + while((double)changeTotal / vSize < getMaxFeeRate() && !walletUtxos.isEmpty() && !cancelTransaction) { //If there is insufficient change output, include another random UTXO so the fee can be increased BlockTransactionHashIndex utxo = walletUtxos.remove(0); utxos.add(utxo); @@ -286,6 +286,15 @@ public class EntryCell extends TreeTableCell { return; } + if(cancelTransaction) { + Payment existing = payments.get(0); + Address address = transactionEntry.getWallet().getFreshNode(KeyPurpose.CHANGE).getAddress(); + Payment payment = new Payment(address, existing.getLabel(), existing.getAmount(), true); + payments.clear(); + payments.add(payment); + opReturns.clear(); + } + EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), utxos)); Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, payments, opReturns.isEmpty() ? null : opReturns, blockTransaction.getFee(), true))); } @@ -417,6 +426,12 @@ public class EntryCell extends TreeTableCell { return increaseFeeGlyph; } + private static Glyph getCancelTransactionRBFGlyph() { + Glyph cancelTxGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.BAN); + cancelTxGlyph.setFontSize(12); + return cancelTxGlyph; + } + private static Glyph getIncreaseFeeCPFPGlyph() { Glyph cpfpGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SIGN_OUT_ALT); cpfpGlyph.setFontSize(12); @@ -475,12 +490,23 @@ public class EntryCell extends TreeTableCell { increaseFee.setGraphic(getIncreaseFeeRBFGlyph()); increaseFee.setOnAction(AE -> { hide(); - increaseFee(transactionEntry); + increaseFee(transactionEntry, false); }); getItems().add(increaseFee); } + if(blockTransaction.getTransaction().isReplaceByFee() && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) { + MenuItem cancelTx = new MenuItem("Cancel Transaction (RBF)"); + cancelTx.setGraphic(getCancelTransactionRBFGlyph()); + cancelTx.setOnAction(AE -> { + hide(); + increaseFee(transactionEntry, true); + }); + + getItems().add(cancelTx); + } + if(containsWalletOutputs(transactionEntry)) { MenuItem createCpfp = new MenuItem("Increase Effective Fee (CPFP)"); createCpfp.setGraphic(getIncreaseFeeCPFPGlyph());