Skip to content

Commit

Permalink
Add option to add opReturn data at BSQ wallet send screen for non-BSQ…
Browse files Browse the repository at this point in the history
… balance.

This can be used for providing proof of ownership of a genesis output when attaching a custom BM receiver address.
See: bisq-network/proposals#390 (comment)

Signed-off-by: HenrikJannsen <[email protected]>
  • Loading branch information
HenrikJannsen committed Nov 27, 2022
1 parent 2acaef5 commit 4bb5a14
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ private Transaction getPreparedSendTx(String receiverAddress,
coinSelector.setUtxoCandidates(null); // We reuse the selectors. Reset the transactionOutputCandidates field
return tx;
} catch (InsufficientMoneyException e) {
log.error("getPreparedSendTx: tx={}", tx.toString());
log.error("getPreparedSendTx: tx={}", tx);
log.error(e.toString());
throw new InsufficientBsqException(e.missing);
}
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2474,6 +2474,10 @@ dao.wallet.send.receiverBtcAddress=Receiver's BTC address
dao.wallet.send.setDestinationAddress=Fill in your destination address
dao.wallet.send.send=Send BSQ funds
dao.wallet.send.inputControl=Select inputs
dao.wallet.send.addOpReturn=Add data
dao.wallet.send.preImage=Pre-image
dao.wallet.send.opReturnAsHex=Op-Return data Hex encoded
dao.wallet.send.opReturnAsHash=Hash of Op-Return data
dao.wallet.send.sendBtc=Send BTC funds
dao.wallet.send.sendFunds.headline=Confirm withdrawal request
dao.wallet.send.sendFunds.details=Sending: {0}\nTo receiving address: {1}.\nRequired mining fee is: {2} ({3} satoshis/vbyte)\nTransaction vsize: {4} vKb\n\nThe recipient will receive: {5}\n\nAre you sure you want to withdraw that amount?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@
import bisq.network.p2p.P2PService;

import bisq.common.UserThread;
import bisq.common.crypto.Hash;
import bisq.common.handlers.ResultHandler;
import bisq.common.util.Hex;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;

import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
Expand All @@ -72,8 +75,13 @@
import javax.inject.Inject;
import javax.inject.Named;

import com.google.common.base.Charsets;

import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;

import javafx.beans.value.ChangeListener;

Expand All @@ -87,6 +95,7 @@

import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelTextField;

@FxmlView
public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
Expand All @@ -106,12 +115,15 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
private final WalletPasswordWindow walletPasswordWindow;

private int gridRow = 0;
private InputTextField amountInputTextField, btcAmountInputTextField;
private Button sendBsqButton, sendBtcButton, bsqInputControlButton, btcInputControlButton;
private InputTextField amountInputTextField, btcAmountInputTextField, preImageTextField;
private TextField opReturnDataAsHexTextField;
private VBox opReturnDataAsHexBox;
private Label opReturnDataAsHexLabel;
private Button sendBsqButton, sendBtcButton, bsqInputControlButton, btcInputControlButton, btcOpReturnButton;
private InputTextField receiversAddressInputTextField, receiversBtcAddressInputTextField;
private ChangeListener<Boolean> focusOutListener;
private TitledGroupBg btcTitledGroupBg;
private ChangeListener<String> inputTextFieldListener;
private ChangeListener<String> inputTextFieldListener, preImageInputTextFieldListener;
@Nullable
private Set<TransactionOutput> bsqUtxoCandidates;
@Nullable
Expand Down Expand Up @@ -166,6 +178,8 @@ public void initialize() {
};
inputTextFieldListener = (observable, oldValue, newValue) -> onUpdateBalances();

preImageInputTextFieldListener = (observable, oldValue, newValue) -> opReturnDataAsHexTextField.setText(getOpReturnDataAsHexFromPreImage(newValue));

setSendBtcGroupVisibleState(false);
}

Expand All @@ -183,6 +197,7 @@ protected void activate() {
bsqInputControlButton.setOnAction((event) -> onBsqInputControl());
sendBtcButton.setOnAction((event) -> onSendBtc());
btcInputControlButton.setOnAction((event) -> onBtcInputControl());
btcOpReturnButton.setOnAction((event) -> onShowPreImageField());

receiversAddressInputTextField.focusedProperty().addListener(focusOutListener);
amountInputTextField.focusedProperty().addListener(focusOutListener);
Expand All @@ -193,6 +208,7 @@ protected void activate() {
amountInputTextField.textProperty().addListener(inputTextFieldListener);
receiversBtcAddressInputTextField.textProperty().addListener(inputTextFieldListener);
btcAmountInputTextField.textProperty().addListener(inputTextFieldListener);
preImageTextField.textProperty().addListener(preImageInputTextFieldListener);

bsqWalletService.addBsqBalanceListener(this);

Expand Down Expand Up @@ -228,11 +244,13 @@ protected void deactivate() {
amountInputTextField.textProperty().removeListener(inputTextFieldListener);
receiversBtcAddressInputTextField.textProperty().removeListener(inputTextFieldListener);
btcAmountInputTextField.textProperty().removeListener(inputTextFieldListener);
preImageTextField.textProperty().removeListener(preImageInputTextFieldListener);

bsqWalletService.removeBsqBalanceListener(this);

sendBsqButton.setOnAction(null);
btcInputControlButton.setOnAction(null);
btcOpReturnButton.setOnAction(null);
sendBtcButton.setOnAction(null);
bsqInputControlButton.setOnAction(null);
}
Expand Down Expand Up @@ -367,12 +385,14 @@ private void setSendBtcGroupVisibleState(boolean visible) {
btcAmountInputTextField.setVisible(visible);
sendBtcButton.setVisible(visible);
btcInputControlButton.setVisible(visible);
btcOpReturnButton.setVisible(visible);

btcTitledGroupBg.setManaged(visible);
receiversBtcAddressInputTextField.setManaged(visible);
btcAmountInputTextField.setManaged(visible);
sendBtcButton.setManaged(visible);
btcInputControlButton.setManaged(visible);
btcOpReturnButton.setManaged(visible);
}

private void addSendBtcGroup() {
Expand All @@ -387,10 +407,24 @@ private void addSendBtcGroup() {
btcAmountInputTextField.setValidator(btcValidator);
GridPane.setColumnSpan(btcAmountInputTextField, 3);

Tuple2<Button, Button> tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow,
Res.get("dao.wallet.send.sendBtc"), Res.get("dao.wallet.send.inputControl"));
preImageTextField = addInputTextField(root, ++gridRow, Res.get("dao.wallet.send.preImage"));
GridPane.setColumnSpan(preImageTextField, 3);
preImageTextField.setVisible(false);
preImageTextField.setManaged(false);

Tuple3<Label, TextField, VBox> opReturnDataAsHexTuple = addTopLabelTextField(root, ++gridRow, Res.get("dao.wallet.send.opReturnAsHex"), -10);
opReturnDataAsHexLabel = opReturnDataAsHexTuple.first;
opReturnDataAsHexTextField = opReturnDataAsHexTuple.second;
opReturnDataAsHexBox = opReturnDataAsHexTuple.third;
GridPane.setColumnSpan(opReturnDataAsHexBox, 3);
opReturnDataAsHexBox.setVisible(false);
opReturnDataAsHexBox.setManaged(false);

Tuple3<Button, Button, Button> tuple = FormBuilder.add3ButtonsAfterGroup(root, ++gridRow,
Res.get("dao.wallet.send.sendBtc"), Res.get("dao.wallet.send.inputControl"), Res.get("dao.wallet.send.addOpReturn"));
sendBtcButton = tuple.first;
btcInputControlButton = tuple.second;
btcOpReturnButton = tuple.third;
}

private void onBtcInputControl() {
Expand All @@ -410,6 +444,15 @@ private void onBtcInputControl() {
show();
}

private void onShowPreImageField() {
btcOpReturnButton.setDisable(true);
preImageTextField.setManaged(true);
preImageTextField.setVisible(true);
opReturnDataAsHexBox.setManaged(true);
opReturnDataAsHexBox.setVisible(true);
GridPane.setRowSpan(btcTitledGroupBg, 4);
}

private void setBtcUtxoCandidates(Set<TransactionOutput> candidates) {
this.btcUtxoCandidates = candidates;
updateBtcValidator(getSpendableBtcBalance());
Expand All @@ -430,8 +473,12 @@ private void onSendBtc() {
String receiversAddressString = receiversBtcAddressInputTextField.getText();
Coin receiverAmount = bsqFormatter.parseToBTC(btcAmountInputTextField.getText());
try {
byte[] opReturnData = null;
if (preImageTextField.isVisible() && !preImageTextField.getText().trim().isEmpty()) {
opReturnData = getOpReturnData(preImageTextField.getText());
}
Transaction preparedSendTx = bsqWalletService.getPreparedSendBtcTx(receiversAddressString, receiverAmount, btcUtxoCandidates);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx);
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedSendTx, opReturnData);
Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
Coin miningFee = signedTx.getFee();

Expand All @@ -449,6 +496,7 @@ private void onSendBtc() {
() -> {
receiversBtcAddressInputTextField.setText("");
btcAmountInputTextField.setText("");
preImageTextField.clear();

receiversBtcAddressInputTextField.resetValidation();
btcAmountInputTextField.resetValidation();
Expand All @@ -463,6 +511,30 @@ private void onSendBtc() {
}
}

private byte[] getOpReturnData(String preImageAsString) {
byte[] opReturnData;
try {
// If preImage is hex encoded we use it directly
opReturnData = Hex.decode(preImageAsString);
} catch (Throwable ignore) {
opReturnData = preImageAsString.getBytes(Charsets.UTF_8);
}

// If too long for OpReturn we hash it
if (opReturnData.length > 80) {
opReturnData = Hash.getSha256Ripemd160hash(opReturnData);
opReturnDataAsHexLabel.setText(Res.get("dao.wallet.send.opReturnAsHash"));
} else {
opReturnDataAsHexLabel.setText(Res.get("dao.wallet.send.opReturnAsHex"));
}

return opReturnData;
}

private String getOpReturnDataAsHexFromPreImage(String preImage) {
return Hex.encode(getOpReturnData(preImage));
}

private void handleError(Throwable t) {
if (t instanceof InsufficientMoneyException) {
final Coin missingCoin = ((InsufficientMoneyException) t).missing;
Expand Down

0 comments on commit 4bb5a14

Please sign in to comment.