mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
add create cpfp transaction functionality
This commit is contained in:
parent
3ff626e2aa
commit
cd00535212
4 changed files with 90 additions and 4 deletions
|
@ -92,6 +92,17 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
|
|||
actionBox.getChildren().add(increaseFeeButton);
|
||||
}
|
||||
|
||||
if(blockTransaction.getHeight() <= 0 && containsWalletOutputs(transactionEntry)) {
|
||||
Button cpfpButton = new Button("");
|
||||
Glyph cpfpGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SIGN_OUT_ALT);
|
||||
cpfpGlyph.setFontSize(12);
|
||||
cpfpButton.setGraphic(cpfpGlyph);
|
||||
cpfpButton.setOnAction(event -> {
|
||||
createCpfp(transactionEntry);
|
||||
});
|
||||
actionBox.getChildren().add(cpfpButton);
|
||||
}
|
||||
|
||||
setGraphic(actionBox);
|
||||
} else if(entry instanceof NodeEntry) {
|
||||
NodeEntry nodeEntry = (NodeEntry)entry;
|
||||
|
@ -245,6 +256,37 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
|
|||
return AppServices.getTargetBlockFeeRates().values().iterator().next();
|
||||
}
|
||||
|
||||
private static void createCpfp(TransactionEntry transactionEntry) {
|
||||
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
||||
List<BlockTransactionHashIndex> ourOutputs = transactionEntry.getChildren().stream()
|
||||
.filter(e -> e instanceof HashIndexEntry)
|
||||
.map(e -> (HashIndexEntry)e)
|
||||
.filter(e -> e.getType().equals(HashIndexEntry.Type.OUTPUT))
|
||||
.map(HashIndexEntry::getHashIndex)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if(ourOutputs.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot create CPFP without any wallet outputs to spend");
|
||||
}
|
||||
|
||||
BlockTransactionHashIndex utxo = ourOutputs.get(0);
|
||||
|
||||
WalletNode freshNode = transactionEntry.getWallet().getFreshNode(KeyPurpose.RECEIVE);
|
||||
String label = transactionEntry.getLabel() == null ? "" : transactionEntry.getLabel();
|
||||
label += (label.isEmpty() ? "" : " ") + "(CPFP)";
|
||||
Payment payment = new Payment(transactionEntry.getWallet().getAddress(freshNode), label, utxo.getValue(), true);
|
||||
|
||||
EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), List.of(utxo)));
|
||||
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), List.of(utxo), List.of(payment), blockTransaction.getFee(), false)));
|
||||
}
|
||||
|
||||
private static boolean containsWalletOutputs(TransactionEntry transactionEntry) {
|
||||
return transactionEntry.getChildren().stream()
|
||||
.filter(e -> e instanceof HashIndexEntry)
|
||||
.map(e -> (HashIndexEntry)e)
|
||||
.anyMatch(e -> e.getType().equals(HashIndexEntry.Type.OUTPUT));
|
||||
}
|
||||
|
||||
private static void sendSelectedUtxos(TreeTableView<Entry> treeTableView, HashIndexEntry hashIndexEntry) {
|
||||
List<HashIndexEntry> utxoEntries = treeTableView.getSelectionModel().getSelectedCells().stream()
|
||||
.map(tp -> tp.getTreeItem().getValue())
|
||||
|
@ -286,7 +328,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
|
|||
getItems().add(copyTxid);
|
||||
|
||||
if(blockTransaction.getTransaction().isReplaceByFee() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
||||
MenuItem increaseFee = new MenuItem("Increase Fee");
|
||||
MenuItem increaseFee = new MenuItem("Increase Fee (RBF)");
|
||||
increaseFee.setOnAction(AE -> {
|
||||
hide();
|
||||
increaseFee(transactionEntry);
|
||||
|
@ -294,6 +336,16 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
|
|||
|
||||
getItems().add(increaseFee);
|
||||
}
|
||||
|
||||
if(containsWalletOutputs(transactionEntry)) {
|
||||
MenuItem createCpfp = new MenuItem("Increase Effective Fee (CPFP)");
|
||||
createCpfp.setOnAction(AE -> {
|
||||
hide();
|
||||
createCpfp(transactionEntry);
|
||||
});
|
||||
|
||||
getItems().add(createCpfp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,12 +48,14 @@ public class FontAwesome5 extends GlyphFont {
|
|||
SATELLITE_DISH('\uf7c0'),
|
||||
SD_CARD('\uf7c2'),
|
||||
SEARCH('\uf002'),
|
||||
SIGN_OUT_ALT('\uf2f5'),
|
||||
SQUARE('\uf0c8'),
|
||||
TIMES_CIRCLE('\uf057'),
|
||||
TOGGLE_OFF('\uf204'),
|
||||
TOGGLE_ON('\uf205'),
|
||||
TOOLS('\uf7d9'),
|
||||
UNDO('\uf0e2'),
|
||||
USER_FRIENDS('\uf500'),
|
||||
WALLET('\uf555');
|
||||
|
||||
private final char ch;
|
||||
|
|
|
@ -81,6 +81,9 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
@FXML
|
||||
private CopyableLabel feeRate;
|
||||
|
||||
@FXML
|
||||
private Label cpfpFeeRate;
|
||||
|
||||
@FXML
|
||||
private Label feeRatePriority;
|
||||
|
||||
|
@ -281,6 +284,8 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
|
||||
FeeRatesSelection feeRatesSelection = Config.get().getFeeRatesSelection();
|
||||
feeRatesSelection = (feeRatesSelection == null ? FeeRatesSelection.MEMPOOL_SIZE : feeRatesSelection);
|
||||
cpfpFeeRate.managedProperty().bind(cpfpFeeRate.visibleProperty());
|
||||
cpfpFeeRate.setVisible(false);
|
||||
setDefaultFeeRate();
|
||||
updateFeeRateSelection(feeRatesSelection);
|
||||
feeSelectionToggleGroup.selectToggle(feeRatesSelection == FeeRatesSelection.BLOCK_TARGET ? targetBlocksToggle : mempoolSizeToggle);
|
||||
|
@ -346,6 +351,7 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
}
|
||||
|
||||
setFeeRate(feeRate);
|
||||
setEffectiveFeeRate(walletTransaction);
|
||||
}
|
||||
|
||||
transactionDiagram.update(walletTransaction);
|
||||
|
@ -354,7 +360,7 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
|
||||
transactionDiagram.sceneProperty().addListener((observable, oldScene, newScene) -> {
|
||||
if(oldScene == null && newScene != null) {
|
||||
transactionDiagram.update(null);
|
||||
transactionDiagram.update(walletTransactionProperty.get());
|
||||
newScene.getWindow().heightProperty().addListener((observable1, oldValue, newValue) -> {
|
||||
transactionDiagram.update(walletTransactionProperty.get());
|
||||
});
|
||||
|
@ -636,10 +642,28 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
}
|
||||
|
||||
private void setFeeRate(Double feeRateAmt) {
|
||||
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte");
|
||||
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vB");
|
||||
setFeeRatePriority(feeRateAmt);
|
||||
}
|
||||
|
||||
private void setEffectiveFeeRate(WalletTransaction walletTransaction) {
|
||||
List<BlockTransaction> unconfirmedUtxoTxs = walletTransaction.getSelectedUtxos().keySet().stream().filter(ref -> ref.getHeight() <= 0)
|
||||
.map(ref -> getWalletForm().getWallet().getTransactions().get(ref.getHash())).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if(!unconfirmedUtxoTxs.isEmpty()) {
|
||||
cpfpFeeRate.setVisible(true);
|
||||
long utxoTxFee = unconfirmedUtxoTxs.stream().mapToLong(BlockTransaction::getFee).sum();
|
||||
double utxoTxSize = unconfirmedUtxoTxs.stream().mapToDouble(blkTx -> blkTx.getTransaction().getVirtualSize()).sum();
|
||||
long thisFee = walletTransaction.getFee();
|
||||
double thisSize = walletTransaction.getTransaction().getVirtualSize();
|
||||
double effectiveRate = (utxoTxFee + thisFee) / (utxoTxSize + thisSize);
|
||||
Tooltip tooltip = new Tooltip(String.format("%.2f", effectiveRate) + " sats/vB effective rate");
|
||||
cpfpFeeRate.setTooltip(tooltip);
|
||||
cpfpFeeRate.setVisible(true);
|
||||
} else {
|
||||
cpfpFeeRate.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setFeeRatePriority(Double feeRateAmt) {
|
||||
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
||||
Integer targetBlocks = getTargetBlocks(feeRateAmt);
|
||||
|
@ -961,7 +985,7 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
List<BlockTransactionHashIndex> utxos = event.getUtxos();
|
||||
utxoSelectorProperty.set(new PresetUtxoSelector(utxos));
|
||||
utxoFilterProperty.set(null);
|
||||
updateTransaction(event.getPayments() == null);
|
||||
updateTransaction(event.getPayments() == null || event.getPayments().stream().anyMatch(Payment::isSendMax));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,14 @@
|
|||
</Field>
|
||||
<Field fx:id="feeRateField" text="Rate:">
|
||||
<CopyableLabel fx:id="feeRate" />
|
||||
<Label fx:id="cpfpFeeRate" text="CPFP">
|
||||
<graphic>
|
||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="SIGN_OUT_ALT" />
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets left="10"/>
|
||||
</padding>
|
||||
</Label>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Label fx:id="feeRatePriority" graphicTextGap="5" contentDisplay="RIGHT">
|
||||
<graphic>
|
||||
|
|
Loading…
Reference in a new issue