- Added code to create coinjoin PSBT

This commit is contained in:
QcMrHyde 2025-06-02 04:12:25 -04:00
parent 055bc65bff
commit 550df1d91a
6 changed files with 85 additions and 88 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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() {

View file

@ -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;

View file

@ -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<UtxoSelector> 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<TxoFilter> 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<Payment> payments = List.of(coinjoinPayment, changePayment);
ArrayList<Payment> payments = new ArrayList<Payment>();
while(satsLeft > denomination + dustThreshold) {
Payment payment = new Payment(sendAddress, paymentLabel, denomination, false);
satsLeft -= denomination;
payment.setType(Payment.Type.COINJOIN);
payments.add(payment);
}
List<byte[]> opReturns = null;
List<UtxoSelector> 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<WalletNode> excludedChangeNodes;
/// TODO fill excludedChangeNodes
SpentTxoFilter spentTxoFilter = new SpentTxoFilter(null);
List<TxoFilter> 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<byte[]> opReturns = new ArrayList<>();
TreeSet<WalletNode> 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);
}
}

View file

@ -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);