From 550df1d91a9944321164434e9cd43b4cbcda78d7 Mon Sep 17 00:00:00 2001 From: QcMrHyde Date: Mon, 2 Jun 2025 04:12:25 -0400 Subject: [PATCH] - Added code to create coinjoin PSBT --- .../sparrowwallet/sparrow/AppController.java | 2 +- .../sparrow/joinstr/JoinstrController.java | 35 +++--- .../sparrow/joinstr/JoinstrForm.java | 17 +-- .../joinstr/JoinstrFormController.java | 7 +- .../sparrow/joinstr/JoinstrPool.java | 106 ++++++++---------- .../sparrow/joinstr/NewPoolController.java | 6 + 6 files changed, 85 insertions(+), 88 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 32d142b0..df2286ab 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -556,7 +556,7 @@ public class AppController implements Initializable { stage.initOwner(tabs.getScene().getWindow()); JoinstrController controller = loader.getController(); - JoinstrForm joinstrForm = new JoinstrForm(getSelectedWalletForm().getStorage(), getSelectedWalletForm().getWallet()); + JoinstrForm joinstrForm = new JoinstrForm(getSelectedWalletForm()); controller.setJoinstrForm(joinstrForm); Scene scene = new Scene(root); diff --git a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrController.java b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrController.java index 35a60a25..522ba5c5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrController.java +++ b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrController.java @@ -57,26 +57,21 @@ public class JoinstrController extends JoinstrFormController { } try { - URL url = AppServices.class.getResource("joinstr/" + display.toString().toLowerCase(Locale.ROOT) + ".fxml"); - if(url == null) { - throw new IllegalStateException("Cannot find joinstr/" + display.toString().toLowerCase(Locale.ROOT) + ".fxml"); - } - - FXMLLoader displayLoader = new FXMLLoader(url); - Node joinstrDisplay = displayLoader.load(); - - if(!existing) { - - joinstrDisplay.setUserData(display); - joinstrDisplay.setViewOrder(1); - - joinstrPane.getChildren().add(joinstrDisplay); - } - - JoinstrFormController controller = displayLoader.getController(); - JoinstrForm joinstrForm = getJoinstrForm(); - controller.setJoinstrForm(joinstrForm); - controller.initializeView(); + URL url = AppServices.class.getResource("joinstr/" + display.toString().toLowerCase(Locale.ROOT) + ".fxml"); + if(url == null) { + throw new IllegalStateException("Cannot find joinstr/" + display.toString().toLowerCase(Locale.ROOT) + ".fxml"); + } + FXMLLoader displayLoader = new FXMLLoader(url); + Node joinstrDisplay = displayLoader.load(); + if(!existing) { + joinstrDisplay.setUserData(display); + joinstrDisplay.setViewOrder(1); + joinstrPane.getChildren().add(joinstrDisplay); + } + JoinstrFormController controller = displayLoader.getController(); + JoinstrForm joinstrForm = getJoinstrForm(); + controller.setJoinstrForm(joinstrForm); + controller.initializeView(); } catch (IOException e) { throw new IllegalStateException("Can't find pane", e); diff --git a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrForm.java b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrForm.java index 03807dfd..019ec6f1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrForm.java @@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.joinstr; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.io.Storage; +import com.sparrowwallet.sparrow.wallet.WalletForm; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -10,20 +11,22 @@ public class JoinstrForm { private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false); - private final Storage storage; - protected Wallet wallet; + private final WalletForm walletForm; - public JoinstrForm(Storage storage, Wallet currentWallet) { - this.storage = storage; - this.wallet = currentWallet; + public JoinstrForm(WalletForm walletForm) { + this.walletForm = walletForm; + } + + public WalletForm getWalletForm() { + return walletForm; } public Wallet getWallet() { - return wallet; + return walletForm.getWallet(); } public Storage getStorage() { - return storage; + return walletForm.getStorage(); } public BooleanProperty lockedProperty() { diff --git a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrFormController.java b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrFormController.java index 9e631e55..bdd3c3a7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrFormController.java +++ b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrFormController.java @@ -1,10 +1,15 @@ package com.sparrowwallet.sparrow.joinstr; import com.sparrowwallet.sparrow.BaseController; +import com.sparrowwallet.sparrow.wallet.WalletForm; public abstract class JoinstrFormController extends BaseController { - public JoinstrForm joinstrForm; + private JoinstrForm joinstrForm; + + public WalletForm getWalletForm() { + return joinstrForm.getWalletForm(); + } public JoinstrForm getJoinstrForm() { return joinstrForm; diff --git a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrPool.java b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrPool.java index 29bc6257..cda16b7f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrPool.java +++ b/src/main/java/com/sparrowwallet/sparrow/joinstr/JoinstrPool.java @@ -3,20 +3,32 @@ package com.sparrowwallet.sparrow.joinstr; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.protocol.TransactionOutput; import com.sparrowwallet.drongo.psbt.PSBT; +import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; +import com.sparrowwallet.drongo.wallet.BnBUtxoSelector; import com.sparrowwallet.drongo.wallet.CoinbaseTxoFilter; import com.sparrowwallet.drongo.wallet.FrozenTxoFilter; +import com.sparrowwallet.drongo.wallet.InsufficientFundsException; +import com.sparrowwallet.drongo.wallet.KnapsackUtxoSelector; import com.sparrowwallet.drongo.wallet.Payment; import com.sparrowwallet.drongo.wallet.SpentTxoFilter; +import com.sparrowwallet.drongo.wallet.StonewallUtxoSelector; import com.sparrowwallet.drongo.wallet.TxoFilter; import com.sparrowwallet.drongo.wallet.UtxoSelector; +import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.drongo.wallet.WalletTransaction; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.control.QRScanDialog; +import com.sparrowwallet.sparrow.io.WalletTransactions; import com.sparrowwallet.sparrow.net.Tor; +import com.sparrowwallet.sparrow.wallet.Entry; import com.sparrowwallet.sparrow.wallet.NodeEntry; +import com.sparrowwallet.sparrow.wallet.OptimizationStrategy; +import com.sparrowwallet.sparrow.wallet.UtxoEntry; import com.sparrowwallet.sparrow.wallet.WalletForm; +import com.sparrowwallet.sparrow.wallet.WalletUtxosEntry; import java.util.*; @@ -66,21 +78,7 @@ public class JoinstrPool { return freshNodeEntry.getAddress(); } - public void createPSBT() { - - Address sendAddress = getNewSendAddress(); - - /* String to byte[] - - if(Utils.isBase64(string) && !Utils.isHex(string)) { - addTransactionTab(name, file, Base64.getDecoder().decode(string)); - } else if(Utils.isHex(string)) { - addTransactionTab(name, file, Utils.hexToBytes(string)); - } else { - throw new ParseException("Input is not base64 or hex", 0); - } - - */ + public void createPSBT() throws InsufficientFundsException { /* PSBT from byte[] @@ -102,61 +100,51 @@ public class JoinstrPool { result.psbt; */ - /* PSBT from TransactionData - TransactionData txdata; - /// TODO fill txdata - psbt = txdata.getPsbt(); - */ - // PSBT from WalletTransaction - List utxoSelectors; - /// TODO select UTXO for transaction + Address sendAddress = getNewSendAddress(); + Wallet wallet = walletForm.getWallet(); + double feeRate = 1.0; + double longTermFeeRate = 10.0; + long fee = 10L; + long dustThreshold = getRecipientDustThreshold(sendAddress); + long amount = wallet.getWalletTxos().keySet().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(); + long satsLeft = amount; - SpentTxoFilter spentTxoFilter = new SpentTxoFilter(null); - List txoFilters = List.of(spentTxoFilter, new FrozenTxoFilter(), new CoinbaseTxoFilter(walletForm.getWallet())); + String paymentLabel = "coinjoin"; - long satsLeft = 0L; - Payment coinjoinPayment = new Payment(sendAddress, "coinjoin", denomination, false); - /// TODO create multiple coinjoin payments for selected UTXO - Payment changePayment = new Payment(sendAddress, "change", satsLeft, false); - List payments = List.of(coinjoinPayment, changePayment); + ArrayList payments = new ArrayList(); + while(satsLeft > denomination + dustThreshold) { + Payment payment = new Payment(sendAddress, paymentLabel, denomination, false); + satsLeft -= denomination; + payment.setType(Payment.Type.COINJOIN); + payments.add(payment); + } - List opReturns = null; + List selectors = new ArrayList<>(); + long noInputsFee = wallet.getNoInputsFee(payments, feeRate); + long costOfChange = wallet.getCostOfChange(feeRate, longTermFeeRate); + selectors.addAll(List.of(new BnBUtxoSelector(noInputsFee, costOfChange), new KnapsackUtxoSelector(noInputsFee))); - Set excludedChangeNodes; - /// TODO fill excludedChangeNodes + SpentTxoFilter spentTxoFilter = new SpentTxoFilter(null); + List txoFilters = List.of(spentTxoFilter, new FrozenTxoFilter(), new CoinbaseTxoFilter(walletForm.getWallet())); - double feeRate = 10.0; - double longTermFeeRate = 10.0; - Long fee = 10L; - Integer currentBlockHeight = AppServices.getCurrentBlockHeight(); - boolean groupByAddress = false; - boolean includeMempoolOutputs = false; + ArrayList opReturns = new ArrayList<>(); + TreeSet excludedChangeNodes = new TreeSet<>(); - WalletTransaction toSign = walletForm.getWallet().createWalletTransaction(utxoSelectors, txoFilters, payments, opReturns, excludedChangeNodes, feeRate, longTermFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs); - PSBT psbt = toSign.createPSBT(); + Integer currentBlockHeight = AppServices.getCurrentBlockHeight(); + boolean groupByAddress = false; + boolean includeMempoolOutputs = false; - // decryptedWallet.sign(psbt); - // decryptedWallet.finalise(psbt); - // Transaction transaction = psbt.extractTransaction(); + WalletTransaction walletTransaction = walletForm.getWallet().createWalletTransaction(selectors, txoFilters, payments, opReturns, excludedChangeNodes, feeRate, longTermFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs); + PSBT psbt = walletTransaction.createPSBT(); - /* PSBT from Transaction - - - PSBT psbt = new PSBT(toSign); - PSBTInput psbtInput = psbt.getPsbtInputs().get(0); - - Transaction toSpend; - /// TODO fill toSpend - TransactionOutput utxoOutput = toSpend.getOutputs().get(0); - psbtInput.setWitnessUtxo(utxoOutput); - - psbtInput.setSigHash(SigHash.ALL); - psbtInput.sign(scriptType.getOutputKey(privKey)); - - */ } + private long getRecipientDustThreshold(Address address) { + TransactionOutput txOutput = new TransactionOutput(new Transaction(), 1L, address.getOutputScript()); + return address.getScriptType().getDustThreshold(txOutput, Transaction.DUST_RELAY_TX_FEE); + } + } diff --git a/src/main/java/com/sparrowwallet/sparrow/joinstr/NewPoolController.java b/src/main/java/com/sparrowwallet/sparrow/joinstr/NewPoolController.java index e6e6413b..71a6532a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/joinstr/NewPoolController.java +++ b/src/main/java/com/sparrowwallet/sparrow/joinstr/NewPoolController.java @@ -1,5 +1,6 @@ package com.sparrowwallet.sparrow.joinstr; +import com.sparrowwallet.sparrow.io.Config; import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.control.Alert; @@ -19,6 +20,7 @@ public class NewPoolController extends JoinstrFormController { @FXML private void handleCreateButton() { try { + String denomination = denominationField.getText().trim(); String peers = peersField.getText().trim(); @@ -49,7 +51,11 @@ public class NewPoolController extends JoinstrFormController { return; } + long denominationSats = Long.valueOf((long) (Double.parseDouble(denomination)*100000000.0D)); + // TODO: Implement pool creation logic here + JoinstrPool pool = new JoinstrPool(getWalletForm(), Config.get().getNostrRelay(),9999, "pubkey", denominationSats); + pool.createPSBT(); /* Alert alert = new Alert(AlertType.INFORMATION);