mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-04 21:36:45 +00:00
allow collaborative sends for linked paynyms, support searching for custom paynyms when initiating collaborative sends
This commit is contained in:
parent
b1940e9293
commit
b16c7345a8
6 changed files with 119 additions and 43 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit d1088fe9ee6da0e53fcfdff020cab3c16c15702b
|
||||
Subproject commit 20f4ac96574dc7bdb811380ff948931663e19c44
|
|
@ -20,6 +20,8 @@ public class PayNym {
|
|||
private final List<PayNym> following;
|
||||
private final List<PayNym> followers;
|
||||
|
||||
private boolean collaborativeSend;
|
||||
|
||||
public PayNym(PaymentCode paymentCode, String nymId, String nymName, boolean segwit, List<PayNym> following, List<PayNym> followers) {
|
||||
this.paymentCode = paymentCode;
|
||||
this.nymId = nymId;
|
||||
|
@ -53,6 +55,14 @@ public class PayNym {
|
|||
return followers;
|
||||
}
|
||||
|
||||
public boolean isCollaborativeSend() {
|
||||
return collaborativeSend;
|
||||
}
|
||||
|
||||
public void setCollaborativeSend(boolean collaborativeSend) {
|
||||
this.collaborativeSend = collaborativeSend;
|
||||
}
|
||||
|
||||
public List<ScriptType> getScriptTypes() {
|
||||
return segwit ? getSegwitScriptTypes() : getV1ScriptTypes();
|
||||
}
|
||||
|
|
|
@ -453,7 +453,7 @@ public class PayNymController {
|
|||
ButtonType previewType = new ButtonType("Preview", ButtonBar.ButtonData.LEFT);
|
||||
ButtonType sendType = new ButtonType("Send", ButtonBar.ButtonData.YES);
|
||||
Optional<ButtonType> optButtonType = AppServices.showAlertDialog("Link PayNym?",
|
||||
"Linking to this contact will allow you to send to it non-collaboratively through unique private addresses you can generate independently.\n\n" +
|
||||
"Linking to this contact will allow you to send to it directly (non-collaboratively) through unique private addresses you can generate independently.\n\n" +
|
||||
"It will cost " + MINIMUM_P2PKH_OUTPUT_SATS + " sats to create the link through a notification transaction, plus the mining fee. Send transaction?", Alert.AlertType.CONFIRMATION, previewType, ButtonType.CANCEL, sendType);
|
||||
if(optButtonType.isPresent() && optButtonType.get() == sendType) {
|
||||
broadcastNotificationTransaction(payNym);
|
||||
|
|
|
@ -9,10 +9,10 @@ import java.io.IOException;
|
|||
|
||||
public class PayNymDialog extends Dialog<PayNym> {
|
||||
public PayNymDialog(String walletId) {
|
||||
this(walletId, false, false);
|
||||
this(walletId, Operation.SHOW, false);
|
||||
}
|
||||
|
||||
public PayNymDialog(String walletId, boolean selectPayNym, boolean selectLinkedOnly) {
|
||||
public PayNymDialog(String walletId, Operation operation, boolean selectLinkedOnly) {
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||
AppServices.onEscapePressed(dialogPane.getScene(), this::close);
|
||||
|
@ -32,11 +32,34 @@ public class PayNymDialog extends Dialog<PayNym> {
|
|||
dialogPane.getStylesheets().add(AppServices.class.getResource("app.css").toExternalForm());
|
||||
dialogPane.getStylesheets().add(AppServices.class.getResource("paynym/paynym.css").toExternalForm());
|
||||
|
||||
final ButtonType sendDirectlyButtonType = new javafx.scene.control.ButtonType("Send Directly", ButtonBar.ButtonData.APPLY);
|
||||
final ButtonType sendCollaborativelyButtonType = new javafx.scene.control.ButtonType("Send Collaboratively", ButtonBar.ButtonData.OK_DONE);
|
||||
final ButtonType selectButtonType = new javafx.scene.control.ButtonType("Select Contact", ButtonBar.ButtonData.APPLY);
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
final ButtonType doneButtonType = new javafx.scene.control.ButtonType("Done", ButtonBar.ButtonData.OK_DONE);
|
||||
|
||||
if(selectPayNym) {
|
||||
if(operation == Operation.SEND) {
|
||||
if(selectLinkedOnly) {
|
||||
dialogPane.getButtonTypes().addAll(sendDirectlyButtonType, cancelButtonType);
|
||||
} else {
|
||||
dialogPane.getButtonTypes().addAll(sendDirectlyButtonType, sendCollaborativelyButtonType, cancelButtonType);
|
||||
Button sendCollaborativelyButton = (Button)dialogPane.lookupButton(sendCollaborativelyButtonType);
|
||||
sendCollaborativelyButton.setDisable(true);
|
||||
sendCollaborativelyButton.setDefaultButton(false);
|
||||
payNymController.payNymProperty().addListener((observable, oldValue, payNym) -> {
|
||||
sendCollaborativelyButton.setDisable(payNym == null);
|
||||
sendCollaborativelyButton.setDefaultButton(payNym != null && !payNymController.isLinked(payNym));
|
||||
});
|
||||
}
|
||||
|
||||
Button sendDirectlyButton = (Button)dialogPane.lookupButton(sendDirectlyButtonType);
|
||||
sendDirectlyButton.setDisable(true);
|
||||
sendDirectlyButton.setDefaultButton(true);
|
||||
payNymController.payNymProperty().addListener((observable, oldValue, payNym) -> {
|
||||
sendDirectlyButton.setDisable(payNym == null || !payNymController.isLinked(payNym));
|
||||
sendDirectlyButton.setDefaultButton(!sendDirectlyButton.isDisable());
|
||||
});
|
||||
} else if(operation == Operation.SELECT) {
|
||||
dialogPane.getButtonTypes().addAll(selectButtonType, cancelButtonType);
|
||||
Button selectButton = (Button)dialogPane.lookupButton(selectButtonType);
|
||||
selectButton.setDisable(true);
|
||||
|
@ -58,9 +81,25 @@ public class PayNymDialog extends Dialog<PayNym> {
|
|||
EventManager.get().unregister(payNymController);
|
||||
});
|
||||
|
||||
setResultConverter(dialogButton -> dialogButton == selectButtonType ? payNymController.getPayNym() : null);
|
||||
setResultConverter(dialogButton -> {
|
||||
if(dialogButton == sendCollaborativelyButtonType) {
|
||||
PayNym payNym = payNymController.getPayNym();
|
||||
payNym.setCollaborativeSend(true);
|
||||
return payNym;
|
||||
} else if(dialogButton == sendDirectlyButtonType || dialogButton == selectButtonType) {
|
||||
PayNym payNym = payNymController.getPayNym();
|
||||
payNym.setCollaborativeSend(false);
|
||||
return payNym;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Operation {
|
||||
SHOW, SELECT, SEND;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,13 @@ import javafx.beans.property.ObjectProperty;
|
|||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
|
@ -148,6 +151,22 @@ public class InitiatorController extends SorobanController {
|
|||
|
||||
private boolean closed;
|
||||
|
||||
private final ChangeListener<String> counterpartyListener = (observable, oldValue, newValue) -> {
|
||||
if(newValue != null) {
|
||||
if(newValue.startsWith("P") && newValue.contains("...") && newValue.length() == 20 && counterpartyPaymentCode.get() != null) {
|
||||
//Assumed valid payment code
|
||||
} else if(Config.get().isUsePayNym() && PAYNYM_REGEX.matcher(newValue).matches()) {
|
||||
if(!newValue.equals(counterpartyPayNymName.get())) {
|
||||
searchPayNyms(newValue);
|
||||
}
|
||||
} else if(!newValue.equals(counterpartyPayNymName.get())) {
|
||||
counterpartyPayNymName.set(null);
|
||||
counterpartyPaymentCode.set(null);
|
||||
payNymAvatar.clearPaymentCode();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public void initializeView(String walletId, Wallet wallet, WalletTransaction walletTransaction) {
|
||||
this.walletId = walletId;
|
||||
this.wallet = wallet;
|
||||
|
@ -247,29 +266,11 @@ public class InitiatorController extends SorobanController {
|
|||
return change;
|
||||
};
|
||||
counterparty.setTextFormatter(new TextFormatter<>(paymentCodeFilter));
|
||||
|
||||
counterparty.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null) {
|
||||
if(newValue.startsWith("P") && newValue.contains("...") && newValue.length() == 20 && counterpartyPaymentCode.get() != null) {
|
||||
//Assumed valid payment code
|
||||
} else if(Config.get().isUsePayNym() && PAYNYM_REGEX.matcher(newValue).matches()) {
|
||||
if(!newValue.equals(counterpartyPayNymName.get())) {
|
||||
payNymLoading.setVisible(true);
|
||||
AppServices.getPayNymService().getPayNym(newValue).subscribe(payNym -> {
|
||||
payNymLoading.setVisible(false);
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString()));
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
}, error -> {
|
||||
payNymLoading.setVisible(false);
|
||||
//ignore, probably doesn't exist but will try again on meeting request
|
||||
});
|
||||
}
|
||||
} else {
|
||||
counterpartyPayNymName.set(null);
|
||||
counterpartyPaymentCode.set(null);
|
||||
payNymAvatar.clearPaymentCode();
|
||||
}
|
||||
counterparty.textProperty().addListener(counterpartyListener);
|
||||
counterparty.addEventFilter(KeyEvent.ANY, event -> {
|
||||
if(counterparty.isEditable() && event.getCode() == KeyCode.ENTER) {
|
||||
searchPayNyms(counterparty.getText());
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -308,6 +309,25 @@ public class InitiatorController extends SorobanController {
|
|||
});
|
||||
}
|
||||
|
||||
private void searchPayNyms(String identifier) {
|
||||
payNymLoading.setVisible(true);
|
||||
AppServices.getPayNymService().getPayNym(identifier).subscribe(payNym -> {
|
||||
payNymLoading.setVisible(false);
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString()));
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
counterparty.textProperty().removeListener(counterpartyListener);
|
||||
int caret = counterparty.getCaretPosition();
|
||||
counterparty.setText("");
|
||||
counterparty.setText(payNym.nymName());
|
||||
counterparty.positionCaret(caret);
|
||||
counterparty.textProperty().addListener(counterpartyListener);
|
||||
}, error -> {
|
||||
payNymLoading.setVisible(false);
|
||||
//ignore, probably doesn't exist but will try again on meeting request
|
||||
});
|
||||
}
|
||||
|
||||
private void setPayNymFollowers() {
|
||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
AppServices.getPayNymService().getPayNym(masterWallet.getPaymentCode().toString()).map(PayNym::following).subscribe(followerPayNyms -> {
|
||||
|
@ -617,7 +637,7 @@ public class InitiatorController extends SorobanController {
|
|||
}
|
||||
|
||||
public void findPayNym(ActionEvent event) {
|
||||
PayNymDialog payNymDialog = new PayNymDialog(walletId, true, false);
|
||||
PayNymDialog payNymDialog = new PayNymDialog(walletId, PayNymDialog.Operation.SELECT, false);
|
||||
Optional<PayNym> optPayNym = payNymDialog.showAndWait();
|
||||
optPayNym.ifPresent(payNym -> {
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
|
|
|
@ -155,7 +155,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
|||
openWallets.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue == payNymWallet) {
|
||||
boolean selectLinkedOnly = sendController.getPaymentTabs().getTabs().size() > 1 || !SorobanServices.canWalletMix(sendController.getWalletForm().getWallet());
|
||||
PayNymDialog payNymDialog = new PayNymDialog(sendController.getWalletForm().getWalletId(), true, selectLinkedOnly);
|
||||
PayNymDialog payNymDialog = new PayNymDialog(sendController.getWalletForm().getWalletId(), PayNymDialog.Operation.SEND, selectLinkedOnly);
|
||||
Optional<PayNym> optPayNym = payNymDialog.showAndWait();
|
||||
optPayNym.ifPresent(this::setPayNym);
|
||||
} else if(newValue != null) {
|
||||
|
@ -279,10 +279,14 @@ public class PaymentController extends WalletFormController implements Initializ
|
|||
}
|
||||
|
||||
public void setPayNym(PayNym payNym) {
|
||||
PayNym existingPayNym = payNymProperty.get();
|
||||
payNymProperty.set(payNym);
|
||||
address.setText(payNym.nymName());
|
||||
address.leftProperty().set(getPayNymGlyph());
|
||||
label.requestFocus();
|
||||
if(existingPayNym != null && payNym.nymName().equals(existingPayNym.nymName()) && payNym.isCollaborativeSend() != existingPayNym.isCollaborativeSend()) {
|
||||
sendController.updateTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateMixOnlyStatus() {
|
||||
|
@ -350,25 +354,28 @@ public class PaymentController extends WalletFormController implements Initializ
|
|||
}
|
||||
|
||||
private Address getRecipientAddress() throws InvalidAddressException {
|
||||
if(payNymProperty.get() == null) {
|
||||
PayNym payNym = payNymProperty.get();
|
||||
if(payNym == null) {
|
||||
return Address.fromString(address.getText());
|
||||
}
|
||||
|
||||
try {
|
||||
Wallet recipientBip47Wallet = getWalletForPayNym(payNymProperty.get());
|
||||
if(recipientBip47Wallet != null) {
|
||||
WalletNode sendNode = recipientBip47Wallet.getFreshNode(KeyPurpose.SEND);
|
||||
ECKey pubKey = sendNode.getPubKey();
|
||||
Address address = recipientBip47Wallet.getScriptType().getAddress(pubKey);
|
||||
if(sendController.getPaymentTabs().getTabs().size() > 1 || (getRecipientValueSats() != null && getRecipientValueSats() > getRecipientDustThreshold(address)) || maxButton.isSelected()) {
|
||||
return address;
|
||||
if(!payNym.isCollaborativeSend()) {
|
||||
try {
|
||||
Wallet recipientBip47Wallet = getWalletForPayNym(payNym);
|
||||
if(recipientBip47Wallet != null) {
|
||||
WalletNode sendNode = recipientBip47Wallet.getFreshNode(KeyPurpose.SEND);
|
||||
ECKey pubKey = sendNode.getPubKey();
|
||||
Address address = recipientBip47Wallet.getScriptType().getAddress(pubKey);
|
||||
if(sendController.getPaymentTabs().getTabs().size() > 1 || (getRecipientValueSats() != null && getRecipientValueSats() > getRecipientDustThreshold(address)) || maxButton.isSelected()) {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
} catch(InvalidPaymentCodeException e) {
|
||||
log.error("Error creating payment code from PayNym", e);
|
||||
}
|
||||
} catch(InvalidPaymentCodeException e) {
|
||||
log.error("Error creating payment code from PayNym", e);
|
||||
}
|
||||
|
||||
return new PayNymAddress(payNymProperty.get());
|
||||
return new PayNymAddress(payNym);
|
||||
}
|
||||
|
||||
private Wallet getWalletForPayNym(PayNym payNym) throws InvalidPaymentCodeException {
|
||||
|
|
Loading…
Reference in a new issue