use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.
the class PrivateKeySweepDialog method createTransaction.
private void createTransaction(ECKey privKey, ScriptType scriptType, List<TransactionOutput> txOutputs, Address destAddress) {
ECKey pubKey = ECKey.fromPublicOnly(privKey);
Transaction noFeeTransaction = new Transaction();
long total = 0;
for (TransactionOutput txOutput : txOutputs) {
scriptType.addSpendingInput(noFeeTransaction, txOutput, pubKey, TransactionSignature.dummy(scriptType == P2TR ? TransactionSignature.Type.SCHNORR : TransactionSignature.Type.ECDSA));
total += txOutput.getValue();
}
TransactionOutput sweepOutput = new TransactionOutput(noFeeTransaction, total, destAddress.getOutputScript());
noFeeTransaction.addOutput(sweepOutput);
Double feeRate = AppServices.getDefaultFeeRate();
long fee = (long) Math.ceil(noFeeTransaction.getVirtualSize() * feeRate);
if (feeRate == Transaction.DEFAULT_MIN_RELAY_FEE) {
fee++;
}
long dustThreshold = destAddress.getScriptType().getDustThreshold(sweepOutput, Transaction.DUST_RELAY_TX_FEE);
if (total - fee <= dustThreshold) {
AppServices.showErrorDialog("Insufficient funds", "The unspent outputs for this private key contain insufficient funds to spend (" + total + " sats).");
return;
}
Transaction transaction = new Transaction();
transaction.setVersion(2);
transaction.setLocktime(AppServices.getCurrentBlockHeight() == null ? 0 : AppServices.getCurrentBlockHeight());
for (TransactionInput txInput : noFeeTransaction.getInputs()) {
transaction.addInput(txInput);
}
transaction.addOutput(new TransactionOutput(transaction, total - fee, destAddress.getOutputScript()));
PSBT psbt = new PSBT(transaction);
for (int i = 0; i < txOutputs.size(); i++) {
TransactionOutput utxoOutput = txOutputs.get(i);
TransactionInput txInput = transaction.getInputs().get(i);
PSBTInput psbtInput = psbt.getPsbtInputs().get(i);
psbtInput.setWitnessUtxo(utxoOutput);
if (ScriptType.P2SH.isScriptType(utxoOutput.getScript())) {
psbtInput.setRedeemScript(txInput.getScriptSig().getFirstNestedScript());
}
if (txInput.getWitness() != null) {
psbtInput.setWitnessScript(txInput.getWitness().getWitnessScript());
}
if (!psbtInput.sign(scriptType.getOutputKey(privKey))) {
AppServices.showErrorDialog("Failed to sign", "Failed to sign for transaction output " + utxoOutput.getHash() + ":" + utxoOutput.getIndex());
return;
}
TransactionSignature signature = psbtInput.isTaproot() ? psbtInput.getTapKeyPathSignature() : psbtInput.getPartialSignature(pubKey);
Transaction finalizeTransaction = new Transaction();
TransactionInput finalizedTxInput = scriptType.addSpendingInput(finalizeTransaction, utxoOutput, pubKey, signature);
psbtInput.setFinalScriptSig(finalizedTxInput.getScriptSig());
psbtInput.setFinalScriptWitness(finalizedTxInput.getWitness());
}
setResult(psbt.extractTransaction());
}
use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.
the class HeadersController method signUsbKeystores.
private void signUsbKeystores() {
if (headersForm.getPsbt().isSigned()) {
return;
}
List<String> fingerprints = headersForm.getSigningWallet().getKeystores().stream().map(keystore -> keystore.getKeyDerivation().getMasterFingerprint()).collect(Collectors.toList());
List<Device> signingDevices = AppServices.getDevices().stream().filter(device -> fingerprints.contains(device.getFingerprint())).collect(Collectors.toList());
if (signingDevices.isEmpty() && headersForm.getSigningWallet().getKeystores().stream().noneMatch(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB))) {
return;
}
DeviceSignDialog dlg = new DeviceSignDialog(fingerprints, headersForm.getPsbt());
dlg.initModality(Modality.NONE);
Stage stage = (Stage) dlg.getDialogPane().getScene().getWindow();
stage.setAlwaysOnTop(true);
Optional<PSBT> optionalSignedPsbt = dlg.showAndWait();
if (optionalSignedPsbt.isPresent()) {
PSBT signedPsbt = optionalSignedPsbt.get();
headersForm.getPsbt().combine(signedPsbt);
EventManager.get().post(new PSBTCombinedEvent(headersForm.getPsbt()));
}
}
use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.
the class SendController method createTransaction.
public void createTransaction(ActionEvent event) {
WalletTransaction walletTransaction = walletTransactionProperty.get();
if (log.isDebugEnabled()) {
Map<WalletNode, List<String>> inputHashes = new LinkedHashMap<>();
for (WalletNode node : walletTransaction.getSelectedUtxos().values()) {
List<String> nodeHashes = inputHashes.computeIfAbsent(node, k -> new ArrayList<>());
nodeHashes.add(ElectrumServer.getScriptHash(walletForm.getWallet(), node));
}
Map<WalletNode, List<String>> changeHash = new LinkedHashMap<>();
for (WalletNode changeNode : walletTransaction.getChangeMap().keySet()) {
changeHash.put(changeNode, List.of(ElectrumServer.getScriptHash(walletForm.getWallet(), changeNode)));
}
log.debug("Creating tx " + walletTransaction.getTransaction().getTxId() + ", expecting notifications for \ninputs \n" + inputHashes + " and \nchange \n" + changeHash);
}
addWalletTransactionNodes();
walletForm.setCreatedWalletTransaction(walletTransaction);
PSBT psbt = walletTransaction.createPSBT();
EventManager.get().post(new ViewPSBTEvent(createButton.getScene().getWindow(), walletTransaction.getPayments().get(0).getLabel(), null, psbt));
}
use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.
the class HeadersController method initializeView.
private void initializeView() {
Transaction tx = headersForm.getTransaction();
updateTxId();
version.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, (int) tx.getVersion()));
version.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setVersion(newValue);
if (oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
});
version.setDisable(!headersForm.isEditable());
updateType();
locktimeToggleGroup.selectedToggleProperty().addListener((ov, old_toggle, new_toggle) -> {
if (locktimeToggleGroup.getSelectedToggle() != null) {
String selection = locktimeToggleGroup.getSelectedToggle().getUserData().toString();
if (selection.equals("none")) {
locktimeFieldset.getChildren().remove(locktimeDateField);
locktimeFieldset.getChildren().remove(locktimeBlockField);
locktimeFieldset.getChildren().remove(locktimeNoneField);
locktimeFieldset.getChildren().add(locktimeNoneField);
tx.setLocktime(0);
if (old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
} else if (selection.equals("block")) {
locktimeFieldset.getChildren().remove(locktimeDateField);
locktimeFieldset.getChildren().remove(locktimeBlockField);
locktimeFieldset.getChildren().remove(locktimeNoneField);
locktimeFieldset.getChildren().add(locktimeBlockField);
Integer block = locktimeBlock.getValue();
if (block != null) {
locktimeCurrentHeight.setVisible(headersForm.isEditable() && AppServices.getCurrentBlockHeight() != null && block < AppServices.getCurrentBlockHeight());
futureBlockWarning.setVisible(AppServices.getCurrentBlockHeight() != null && block > AppServices.getCurrentBlockHeight());
tx.setLocktime(block);
if (old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
}
} else {
locktimeFieldset.getChildren().remove(locktimeBlockField);
locktimeFieldset.getChildren().remove(locktimeDateField);
locktimeFieldset.getChildren().remove(locktimeNoneField);
locktimeFieldset.getChildren().add(locktimeDateField);
LocalDateTime date = locktimeDate.getDateTimeValue();
if (date != null) {
locktimeDate.setDateTimeValue(date);
futureDateWarning.setVisible(date.isAfter(LocalDateTime.now()));
tx.setLocktime(date.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
if (old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
}
}
}
});
locktimeCurrentHeight.managedProperty().bind(locktimeCurrentHeight.visibleProperty());
locktimeCurrentHeight.setVisible(false);
futureBlockWarning.managedProperty().bind(futureBlockWarning.visibleProperty());
futureBlockWarning.setVisible(false);
futureDateWarning.managedProperty().bind(futureDateWarning.visibleProperty());
futureDateWarning.setVisible(false);
locktimeNone.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int) Transaction.MAX_BLOCK_LOCKTIME - 1, 0));
if (tx.getLocktime() < Transaction.MAX_BLOCK_LOCKTIME) {
locktimeBlock.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int) Transaction.MAX_BLOCK_LOCKTIME - 1, (int) tx.getLocktime()));
if (tx.getLocktime() == 0) {
locktimeToggleGroup.selectToggle(locktimeNoneType);
} else {
locktimeToggleGroup.selectToggle(locktimeBlockType);
}
LocalDateTime date = Instant.now().atZone(ZoneId.systemDefault()).toLocalDateTime();
locktimeDate.setDateTimeValue(date);
} else {
locktimeBlock.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int) Transaction.MAX_BLOCK_LOCKTIME - 1));
LocalDateTime date = Instant.ofEpochSecond(tx.getLocktime()).atZone(ZoneId.systemDefault()).toLocalDateTime();
locktimeDate.setDateTimeValue(date);
locktimeToggleGroup.selectToggle(locktimeDateType);
}
locktimeBlock.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setLocktime(newValue);
locktimeCurrentHeight.setVisible(headersForm.isEditable() && AppServices.getCurrentBlockHeight() != null && newValue < AppServices.getCurrentBlockHeight());
futureBlockWarning.setVisible(AppServices.getCurrentBlockHeight() != null && newValue > AppServices.getCurrentBlockHeight());
if (oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
EventManager.get().post(new TransactionLocktimeChangedEvent(tx));
}
});
LocalDateTime maxLocktimeDate = Instant.parse(MAX_LOCKTIME_DATE).atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime minLocktimeDate = Instant.parse(MIN_LOCKTIME_DATE).atZone(ZoneId.systemDefault()).toLocalDateTime();
locktimeDate.setDayCellFactory(d -> new DateCell() {
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
setDisable(item.isAfter(maxLocktimeDate.toLocalDate()) || item.isBefore(minLocktimeDate.toLocalDate()));
}
});
locktimeDate.setFormat(LOCKTIME_DATE_FORMAT);
locktimeDate.dateTimeValueProperty().addListener((obs, oldValue, newValue) -> {
int caret = locktimeDate.getEditor().getCaretPosition();
locktimeDate.getEditor().setText(newValue.format(DateTimeFormatter.ofPattern(locktimeDate.getFormat())));
locktimeDate.getEditor().positionCaret(caret);
tx.setLocktime(newValue.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
futureDateWarning.setVisible(newValue.isAfter(LocalDateTime.now()));
if (oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
});
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(LOCKTIME_DATE_FORMAT);
locktimeDate.setConverter(new StringConverter<LocalDate>() {
public String toString(LocalDate object) {
LocalDateTime value = locktimeDate.getDateTimeValue();
return (value != null) ? value.format(formatter) : "";
}
public LocalDate fromString(String value) {
if (value == null) {
locktimeDate.setDateTimeValue(null);
return null;
}
LocalDateTime localDateTime = LocalDateTime.parse(value, formatter);
if (localDateTime.isAfter(maxLocktimeDate) || localDateTime.isBefore(minLocktimeDate)) {
throw new IllegalArgumentException("Invalid locktime date");
}
locktimeDate.setDateTimeValue(localDateTime);
return locktimeDate.getDateTimeValue().toLocalDate();
}
});
locktimeDate.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
String controlValue = locktimeDate.getDateTimeValue().format(DateTimeFormatter.ofPattern(locktimeDate.getFormat()));
if (!controlValue.equals(newValue) && !locktimeDate.getStyleClass().contains("edited")) {
locktimeDate.getStyleClass().add("edited");
}
});
locktimeDate.addEventFilter(KeyEvent.ANY, event -> {
if (event.getCode() == KeyCode.ENTER) {
locktimeDate.getStyleClass().remove("edited");
}
});
boolean locktimeEnabled = headersForm.getTransaction().isLocktimeSequenceEnabled();
locktimeNoneType.setDisable(!headersForm.isEditable() || !locktimeEnabled);
locktimeBlockType.setDisable(!headersForm.isEditable() || !locktimeEnabled);
locktimeDateType.setDisable(!headersForm.isEditable() || !locktimeEnabled);
locktimeBlock.setDisable(!headersForm.isEditable() || !locktimeEnabled);
locktimeDate.setDisable(!headersForm.isEditable() || !locktimeEnabled);
locktimeCurrentHeight.setDisable(!headersForm.isEditable() || !locktimeEnabled);
updateSize();
Long feeAmt = null;
if (headersForm.getPsbt() != null) {
feeAmt = headersForm.getPsbt().getFee();
} else if (headersForm.getTransaction().getInputs().size() == 1 && headersForm.getTransaction().getInputs().get(0).isCoinBase()) {
feeAmt = 0L;
} else if (headersForm.getInputTransactions() != null) {
feeAmt = calculateFee(headersForm.getInputTransactions());
} else {
Wallet wallet = getWalletFromTransactionInputs();
if (wallet != null) {
feeAmt = calculateFee(wallet.getWalletTransactions());
}
}
if (feeAmt != null) {
updateFee(feeAmt);
}
transactionDiagram.update(getWalletTransaction(headersForm.getInputTransactions()));
blockchainForm.managedProperty().bind(blockchainForm.visibleProperty());
signingWalletForm.managedProperty().bind(signingWalletForm.visibleProperty());
sigHashForm.managedProperty().bind(sigHashForm.visibleProperty());
finalizeButtonBox.managedProperty().bind(finalizeButtonBox.visibleProperty());
signaturesForm.managedProperty().bind(signaturesForm.visibleProperty());
signButtonBox.managedProperty().bind(signButtonBox.visibleProperty());
broadcastButtonBox.managedProperty().bind(broadcastButtonBox.visibleProperty());
signaturesProgressBar.managedProperty().bind(signaturesProgressBar.visibleProperty());
broadcastProgressBar.managedProperty().bind(broadcastProgressBar.visibleProperty());
broadcastProgressBar.visibleProperty().bind(signaturesProgressBar.visibleProperty().not());
broadcastButton.managedProperty().bind(broadcastButton.visibleProperty());
saveFinalButton.managedProperty().bind(saveFinalButton.visibleProperty());
saveFinalButton.visibleProperty().bind(broadcastButton.visibleProperty().not());
broadcastButton.visibleProperty().bind(AppServices.onlineProperty());
BitcoinURI payjoinURI = getPayjoinURI();
boolean isPayjoinOriginalTx = payjoinURI != null && headersForm.getPsbt() != null && headersForm.getPsbt().getPsbtInputs().stream().noneMatch(PSBTInput::isFinalized);
payjoinButton.managedProperty().bind(payjoinButton.visibleProperty());
payjoinButton.visibleProperty().set(isPayjoinOriginalTx);
broadcastButton.setDefaultButton(!isPayjoinOriginalTx);
blockchainForm.setVisible(false);
signingWalletForm.setVisible(false);
sigHashForm.setVisible(false);
finalizeButtonBox.setVisible(false);
signaturesForm.setVisible(false);
signButtonBox.setVisible(false);
broadcastButtonBox.setVisible(false);
if (headersForm.getBlockTransaction() != null) {
updateBlockchainForm(headersForm.getBlockTransaction(), AppServices.getCurrentBlockHeight());
} else if (headersForm.getPsbt() != null) {
PSBT psbt = headersForm.getPsbt();
if (headersForm.isEditable()) {
signingWalletForm.setVisible(true);
sigHashForm.setVisible(true);
finalizeButtonBox.setVisible(true);
} else if (headersForm.getPsbt().isSigned()) {
signaturesForm.setVisible(true);
broadcastButtonBox.setVisible(true);
} else {
signingWalletForm.setVisible(true);
finalizeButtonBox.setVisible(true);
finalizeTransaction.setText("Set Signing Wallet");
}
signingWallet.managedProperty().bind(signingWallet.visibleProperty());
noWalletsWarning.managedProperty().bind(noWalletsWarning.visibleProperty());
noWalletsWarningLink.managedProperty().bind(noWalletsWarningLink.visibleProperty());
noWalletsWarningLink.visibleProperty().bind(noWalletsWarning.visibleProperty());
SigHash psbtSigHash = SigHash.ALL;
for (PSBTInput psbtInput : psbt.getPsbtInputs()) {
if (psbtInput.getSigHash() != null) {
psbtSigHash = psbtInput.getSigHash();
}
}
sigHash.setValue(psbtSigHash == SigHash.ALL_TAPROOT ? SigHash.ALL : psbtSigHash);
sigHash.valueProperty().addListener((observable, oldValue, newValue) -> {
for (PSBTInput psbtInput : psbt.getPsbtInputs()) {
psbtInput.setSigHash(psbtInput.isTaproot() && newValue == SigHash.ALL ? SigHash.ALL_TAPROOT : newValue);
}
});
Platform.runLater(this::requestOpenWallets);
}
headersForm.signingWalletProperty().addListener((observable, oldValue, signingWallet) -> {
initializeSignButton(signingWallet);
updateSignedKeystores(signingWallet);
int threshold = signingWallet.getDefaultPolicy().getNumSignaturesRequired();
signaturesProgressBar.initialize(headersForm.getSignatureKeystoreMap(), threshold);
});
blockchainForm.setDynamicUpdate(this);
}
use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.
the class HeadersController method getPayjoinTransaction.
public void getPayjoinTransaction(ActionEvent event) {
BitcoinURI payjoinURI = getPayjoinURI();
if (payjoinURI == null) {
throw new IllegalStateException("No valid Payjoin URI");
}
Payjoin payjoin = new Payjoin(payjoinURI, headersForm.getSigningWallet(), headersForm.getPsbt());
Payjoin.RequestPayjoinPSBTService requestPayjoinPSBTService = new Payjoin.RequestPayjoinPSBTService(payjoin, true);
requestPayjoinPSBTService.setOnSucceeded(successEvent -> {
PSBT proposalPsbt = requestPayjoinPSBTService.getValue();
EventManager.get().post(new ViewPSBTEvent(payjoinButton.getScene().getWindow(), headersForm.getName() + " Payjoin", null, proposalPsbt));
});
requestPayjoinPSBTService.setOnFailed(failedEvent -> {
AppServices.showErrorDialog("Error Requesting Payjoin Transaction", failedEvent.getSource().getException().getMessage());
});
requestPayjoinPSBTService.start();
}
Aggregations