mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-10 00:27:00 +00:00
add pay to paynym via payjoin
This commit is contained in:
@@ -1251,7 +1251,7 @@ public class AppController implements Initializable {
|
||||
try {
|
||||
soroban.setHDWallet(copy);
|
||||
CounterpartyDialog counterpartyDialog = new CounterpartyDialog(selectedWalletForm.getWalletId(), selectedWalletForm.getWallet());
|
||||
if(Network.get() == Network.TESTNET) {
|
||||
if(Config.get().isSameAppMixing()) {
|
||||
counterpartyDialog.initModality(Modality.NONE);
|
||||
}
|
||||
counterpartyDialog.showAndWait();
|
||||
@@ -1278,14 +1278,14 @@ public class AppController implements Initializable {
|
||||
} else {
|
||||
soroban.setHDWallet(wallet);
|
||||
CounterpartyDialog counterpartyDialog = new CounterpartyDialog(selectedWalletForm.getWalletId(), selectedWalletForm.getWallet());
|
||||
if(Network.get() == Network.TESTNET) {
|
||||
if(Config.get().isSameAppMixing()) {
|
||||
counterpartyDialog.initModality(Modality.NONE);
|
||||
}
|
||||
counterpartyDialog.showAndWait();
|
||||
}
|
||||
} else {
|
||||
CounterpartyDialog counterpartyDialog = new CounterpartyDialog(selectedWalletForm.getWalletId(), selectedWalletForm.getWallet());
|
||||
if(Network.get() == Network.TESTNET) {
|
||||
if(Config.get().isSameAppMixing()) {
|
||||
counterpartyDialog.initModality(Modality.NONE);
|
||||
}
|
||||
counterpartyDialog.showAndWait();
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.FollowPayNymEvent;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNym;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNymController;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
public class PayNymCell extends ListCell<PayNym> {
|
||||
private final String walletId;
|
||||
private final PayNymController payNymController;
|
||||
|
||||
public PayNymCell(String walletId) {
|
||||
public PayNymCell(PayNymController payNymController) {
|
||||
super();
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
setContentDisplay(ContentDisplay.LEFT);
|
||||
getStyleClass().add("paynym-cell");
|
||||
setPrefHeight(50);
|
||||
this.walletId = walletId;
|
||||
this.payNymController = payNymController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,11 +46,12 @@ public class PayNymCell extends ListCell<PayNym> {
|
||||
if(getListView().getUserData() == Boolean.TRUE) {
|
||||
HBox hBox = new HBox();
|
||||
hBox.setAlignment(Pos.CENTER);
|
||||
Button button = new Button("Follow");
|
||||
Button button = new Button("Add Contact");
|
||||
hBox.getChildren().add(button);
|
||||
pane.setRight(hBox);
|
||||
button.setOnAction(event -> {
|
||||
EventManager.get().post(new FollowPayNymEvent(walletId, payNym.paymentCode()));
|
||||
button.setDisable(true);
|
||||
payNymController.followPayNym(payNym.paymentCode());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -113,16 +113,18 @@ public class TransactionDiagram extends GridPane {
|
||||
}
|
||||
|
||||
private List<Map<BlockTransactionHashIndex, WalletNode>> getDisplayedUtxoSets() {
|
||||
boolean addUserSet = getOptimizationStrategy() == OptimizationStrategy.PRIVACY && SorobanServices.canWalletMix(walletTx.getWallet())
|
||||
&& walletTx.getPayments().size() == 1
|
||||
&& (walletTx.getPayments().get(0).getAddress().getScriptType() == walletTx.getWallet().getAddress(walletTx.getWallet().getFreshNode(KeyPurpose.RECEIVE)).getScriptType());
|
||||
|
||||
List<Map<BlockTransactionHashIndex, WalletNode>> displayedUtxoSets = new ArrayList<>();
|
||||
for(Map<BlockTransactionHashIndex, WalletNode> selectedUtxoSet : walletTx.getSelectedUtxoSets()) {
|
||||
displayedUtxoSets.add(getDisplayedUtxos(selectedUtxoSet, walletTx.getSelectedUtxoSets().size()));
|
||||
displayedUtxoSets.add(getDisplayedUtxos(selectedUtxoSet, addUserSet ? 2 : walletTx.getSelectedUtxoSets().size()));
|
||||
}
|
||||
|
||||
if(getOptimizationStrategy() == OptimizationStrategy.PRIVACY && displayedUtxoSets.size() == 1 && SorobanServices.canWalletMix(walletTx.getWallet())
|
||||
&& walletTx.getPayments().size() == 1
|
||||
&& (walletTx.getPayments().get(0).getAddress().getScriptType() == walletTx.getWallet().getAddress(walletTx.getWallet().getFreshNode(KeyPurpose.RECEIVE)).getScriptType())) {
|
||||
if(addUserSet && displayedUtxoSets.size() == 1) {
|
||||
Map<BlockTransactionHashIndex, WalletNode> addUserUtxoSet = new HashMap<>();
|
||||
addUserUtxoSet.put(new AddUserBlockTransactionHashIndex(), null);
|
||||
addUserUtxoSet.put(new AddUserBlockTransactionHashIndex(!walletTx.isTwoPersonCoinjoin()), null);
|
||||
displayedUtxoSets.add(addUserUtxoSet);
|
||||
}
|
||||
|
||||
@@ -324,8 +326,14 @@ public class TransactionDiagram extends GridPane {
|
||||
joiner.add(getInputDescription(additionalInput));
|
||||
}
|
||||
tooltip.setText(joiner.toString());
|
||||
} else if(input instanceof InvisibleBlockTransactionHashIndex || input instanceof AddUserBlockTransactionHashIndex) {
|
||||
} else if(input instanceof InvisibleBlockTransactionHashIndex) {
|
||||
tooltip.setText("");
|
||||
} else if(input instanceof AddUserBlockTransactionHashIndex) {
|
||||
tooltip.setText("");
|
||||
label.setGraphic(walletTx.isTwoPersonCoinjoin() ? getQuestionGlyph() : getWarningGlyph());
|
||||
label.setOnMouseClicked(event -> {
|
||||
EventManager.get().post(new SorobanInitiatedEvent(walletTx.getWallet()));
|
||||
});
|
||||
} else {
|
||||
if(walletTx.getInputTransactions() != null && walletTx.getInputTransactions().get(input.getHash()) != null) {
|
||||
BlockTransaction blockTransaction = walletTx.getInputTransactions().get(input.getHash());
|
||||
@@ -773,6 +781,13 @@ public class TransactionDiagram extends GridPane {
|
||||
return feeWarningGlyph;
|
||||
}
|
||||
|
||||
private Glyph getQuestionGlyph() {
|
||||
Glyph feeWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.QUESTION_CIRCLE);
|
||||
feeWarningGlyph.getStyleClass().add("question-icon");
|
||||
feeWarningGlyph.setFontSize(12);
|
||||
return feeWarningGlyph;
|
||||
}
|
||||
|
||||
private Glyph getLockGlyph() {
|
||||
Glyph lockGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.LOCK);
|
||||
lockGlyph.getStyleClass().add("lock-icon");
|
||||
@@ -891,13 +906,16 @@ public class TransactionDiagram extends GridPane {
|
||||
}
|
||||
|
||||
private static class AddUserBlockTransactionHashIndex extends BlockTransactionHashIndex {
|
||||
public AddUserBlockTransactionHashIndex() {
|
||||
private final boolean required;
|
||||
|
||||
public AddUserBlockTransactionHashIndex(boolean required) {
|
||||
super(Sha256Hash.ZERO_HASH, 0, new Date(), 0L, 0, 0);
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return "Add Mix Partner?";
|
||||
return "Add Mix Partner" + (required ? "" : "?");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
|
||||
public class FollowPayNymEvent {
|
||||
private final String walletId;
|
||||
private final PaymentCode paymentCode;
|
||||
|
||||
public FollowPayNymEvent(String walletId, PaymentCode paymentCode) {
|
||||
this.walletId = walletId;
|
||||
this.paymentCode = paymentCode;
|
||||
}
|
||||
|
||||
public String getWalletId() {
|
||||
return walletId;
|
||||
}
|
||||
|
||||
public PaymentCode getPaymentCode() {
|
||||
return paymentCode;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ public class Config {
|
||||
private boolean useProxy;
|
||||
private String proxyServer;
|
||||
private boolean usePayNym;
|
||||
private boolean sameAppMixing;
|
||||
private Double appWidth;
|
||||
private Double appHeight;
|
||||
|
||||
@@ -511,6 +512,15 @@ public class Config {
|
||||
flush();
|
||||
}
|
||||
|
||||
public boolean isSameAppMixing() {
|
||||
return sameAppMixing;
|
||||
}
|
||||
|
||||
public void setSameAppMixing(boolean sameAppMixing) {
|
||||
this.sameAppMixing = sameAppMixing;
|
||||
flush();
|
||||
}
|
||||
|
||||
public Double getAppWidth() {
|
||||
return appWidth;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,9 @@ public class CounterpartyController extends SorobanController {
|
||||
@FXML
|
||||
private Label mixType;
|
||||
|
||||
@FXML
|
||||
private Label mixFee;
|
||||
|
||||
@FXML
|
||||
private ProgressTimer step3Timer;
|
||||
|
||||
@@ -258,7 +261,17 @@ public class CounterpartyController extends SorobanController {
|
||||
});
|
||||
}
|
||||
|
||||
mixType.setText(cahootsType.getLabel());
|
||||
if(cahootsType == CahootsType.STONEWALLX2) {
|
||||
mixType.setText("Two person coinjoin (" + cahootsType.getLabel() + ")");
|
||||
mixFee.setText("You pay half the miner fee");
|
||||
} else if(cahootsType == CahootsType.STOWAWAY) {
|
||||
mixType.setText("Payjoin (" + cahootsType.getLabel() + ")");
|
||||
mixFee.setText("None");
|
||||
} else {
|
||||
mixType.setText(cahootsType.getLabel());
|
||||
mixFee.setText("None");
|
||||
}
|
||||
|
||||
mixDetails.setVisible(true);
|
||||
meetingReceived.set(Boolean.TRUE);
|
||||
}
|
||||
@@ -365,29 +378,7 @@ public class CounterpartyController extends SorobanController {
|
||||
payNymAvatar.setPaymentCode(soroban.getPaymentCode());
|
||||
payNym.setVisible(true);
|
||||
|
||||
if(createMap.get("claimed") == Boolean.FALSE) {
|
||||
soroban.getAuthToken(createMap).subscribe(authToken -> {
|
||||
String signature = soroban.getSignature(authToken);
|
||||
soroban.claimPayNym(authToken, signature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
soroban.addSamouraiPaymentCode(authToken, signature).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
}, error -> {
|
||||
soroban.getAuthToken(new HashMap<>()).subscribe(newAuthToken -> {
|
||||
String newSignature = soroban.getSignature(newAuthToken);
|
||||
soroban.claimPayNym(newAuthToken, newSignature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
soroban.addSamouraiPaymentCode(newAuthToken, newSignature).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
});
|
||||
}, newError -> {
|
||||
log.error("Error claiming PayNym", newError);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
claimPayNym(soroban, createMap);
|
||||
}, error -> {
|
||||
log.error("Error retrieving PayNym", error);
|
||||
AppServices.showErrorDialog("Error retrieving PayNym", error.getMessage());
|
||||
|
||||
@@ -54,7 +54,7 @@ import static com.sparrowwallet.sparrow.soroban.Soroban.TIMEOUT_MS;
|
||||
public class InitiatorController extends SorobanController {
|
||||
private static final Logger log = LoggerFactory.getLogger(InitiatorController.class);
|
||||
|
||||
private static final PayNym FIND_FOLLOWERS = new PayNym(null, null, "Retrieve PayNyms...", false, Collections.emptyList(), Collections.emptyList());
|
||||
private static final PayNym FIND_FOLLOWERS = new PayNym(null, null, "Retrieve Contacts...", false, Collections.emptyList(), Collections.emptyList());
|
||||
|
||||
private String walletId;
|
||||
private Wallet wallet;
|
||||
@@ -118,6 +118,8 @@ public class InitiatorController extends SorobanController {
|
||||
|
||||
private final ObjectProperty<Transaction> transactionProperty = new SimpleObjectProperty<>(null);
|
||||
|
||||
private CahootsType cahootsType = CahootsType.STONEWALLX2;
|
||||
|
||||
public void initializeView(String walletId, Wallet wallet, WalletTransaction walletTransaction) {
|
||||
this.walletId = walletId;
|
||||
this.wallet = wallet;
|
||||
@@ -160,9 +162,6 @@ public class InitiatorController extends SorobanController {
|
||||
payNymLoading.maxHeightProperty().bind(counterparty.heightProperty());
|
||||
payNymLoading.setVisible(false);
|
||||
|
||||
findPayNym.managedProperty().bind(findPayNym.visibleProperty());
|
||||
findPayNym.setVisible(Config.get().isUsePayNym());
|
||||
|
||||
payNymAvatar.managedProperty().bind(payNymAvatar.visibleProperty());
|
||||
payNymFollowers.prefWidthProperty().bind(counterparty.widthProperty());
|
||||
payNymFollowers.valueProperty().addListener((observable, oldValue, payNym) -> {
|
||||
@@ -241,7 +240,17 @@ public class InitiatorController extends SorobanController {
|
||||
}
|
||||
});
|
||||
|
||||
if(Config.get().isUsePayNym()) {
|
||||
Payment payment = walletTransaction.getPayments().get(0);
|
||||
if(payment.getAddress() instanceof PayNymAddress payNymAddress) {
|
||||
PayNym payNym = payNymAddress.getPayNym();
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
counterpartyPaymentCode.set(payNym.paymentCode());
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
counterparty.setText(payNym.nymName());
|
||||
counterparty.setEditable(false);
|
||||
findPayNym.setVisible(false);
|
||||
cahootsType = CahootsType.STOWAWAY;
|
||||
} else if(Config.get().isUsePayNym()) {
|
||||
setPayNymFollowers();
|
||||
} else {
|
||||
List<PayNym> defaultList = new ArrayList<>();
|
||||
@@ -265,7 +274,7 @@ public class InitiatorController extends SorobanController {
|
||||
}, error -> {
|
||||
if(error.getMessage().endsWith("404")) {
|
||||
Config.get().setUsePayNym(false);
|
||||
AppServices.showErrorDialog("Could not retrieve PayNym", "This wallet does not have an associated PayNym or any followers. You can retrieve the PayNym using the Tools menu → Find Mix Partner.");
|
||||
AppServices.showErrorDialog("Could not retrieve PayNym", "This wallet does not have an associated PayNym or any followers yet. You can retrieve the PayNym using the Find PayNym button.");
|
||||
} else {
|
||||
log.warn("Could not retrieve followers: ", error);
|
||||
}
|
||||
@@ -330,7 +339,7 @@ public class InitiatorController extends SorobanController {
|
||||
getPaymentCodeCounterparty(soroban).subscribe(paymentCodeCounterparty -> {
|
||||
try {
|
||||
SorobanCahootsService sorobanMeetingService = soroban.getSorobanCahootsService(initiatorCahootsWallet);
|
||||
sorobanMeetingService.sendMeetingRequest(paymentCodeCounterparty, CahootsType.STONEWALLX2)
|
||||
sorobanMeetingService.sendMeetingRequest(paymentCodeCounterparty, cahootsType)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.subscribe(meetingRequest -> {
|
||||
@@ -387,7 +396,9 @@ public class InitiatorController extends SorobanController {
|
||||
}
|
||||
|
||||
SorobanCahootsService sorobanCahootsService = soroban.getSorobanCahootsService(initiatorCahootsWallet);
|
||||
CahootsContext cahootsContext = CahootsContext.newInitiatorStonewallx2(payment.getAmount(), payment.getAddress().toString());
|
||||
CahootsContext cahootsContext = cahootsType == CahootsType.STONEWALLX2 ?
|
||||
CahootsContext.newInitiatorStonewallx2(payment.getAmount(), payment.getAddress().toString()) :
|
||||
CahootsContext.newInitiatorStowaway(payment.getAmount());
|
||||
|
||||
sorobanCahootsService.getSorobanService().getOnInteraction()
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
|
||||
@@ -48,7 +48,7 @@ public class InitiatorDialog extends Dialog<Transaction> {
|
||||
Button nextButton = (Button)dialogPane.lookupButton(nextButtonType);
|
||||
Button cancelButton = (Button)dialogPane.lookupButton(cancelButtonType);
|
||||
Button broadcastButton = (Button)dialogPane.lookupButton(broadcastButtonType);
|
||||
nextButton.setDisable(true);
|
||||
nextButton.setDisable(initiatorController.counterpartyPaymentCodeProperty().get() == null);
|
||||
broadcastButton.setDisable(true);
|
||||
|
||||
nextButton.managedProperty().bind(nextButton.visibleProperty());
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.sparrowwallet.sparrow.soroban;
|
||||
|
||||
import com.sparrowwallet.drongo.address.P2WPKHAddress;
|
||||
|
||||
public final class PayNymAddress extends P2WPKHAddress {
|
||||
private final PayNym payNym;
|
||||
|
||||
public PayNymAddress(PayNym payNym) {
|
||||
super(new byte[20]);
|
||||
this.payNym = payNym;
|
||||
}
|
||||
|
||||
public PayNym getPayNym() {
|
||||
return payNym;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return payNym.nymName();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
package com.sparrowwallet.sparrow.soroban;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
import com.sparrowwallet.drongo.SecureString;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.crypto.EncryptionType;
|
||||
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
|
||||
import com.sparrowwallet.drongo.crypto.Key;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.*;
|
||||
import com.sparrowwallet.sparrow.event.FollowPayNymEvent;
|
||||
import com.sparrowwallet.sparrow.event.StorageEvent;
|
||||
import com.sparrowwallet.sparrow.event.TimedEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
@@ -23,6 +33,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||
|
||||
public class PayNymController extends SorobanController {
|
||||
private static final Logger log = LoggerFactory.getLogger(PayNymController.class);
|
||||
|
||||
@@ -32,6 +44,9 @@ public class PayNymController extends SorobanController {
|
||||
@FXML
|
||||
private CopyableTextField payNymName;
|
||||
|
||||
@FXML
|
||||
private Button payNymRetrieve;
|
||||
|
||||
@FXML
|
||||
private PaymentCodeTextField paymentCode;
|
||||
|
||||
@@ -57,6 +72,15 @@ public class PayNymController extends SorobanController {
|
||||
public void initializeView(String walletId) {
|
||||
this.walletId = walletId;
|
||||
|
||||
payNymName.managedProperty().bind(payNymName.visibleProperty());
|
||||
payNymRetrieve.managedProperty().bind(payNymRetrieve.visibleProperty());
|
||||
payNymRetrieve.visibleProperty().bind(payNymName.visibleProperty().not());
|
||||
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
if(soroban.getPaymentCode() != null) {
|
||||
paymentCode.setPaymentCode(soroban.getPaymentCode());
|
||||
}
|
||||
|
||||
findNymProperty.addListener((observable, oldValue, nymIdentifier) -> {
|
||||
if(nymIdentifier != null) {
|
||||
searchFollowing(nymIdentifier);
|
||||
@@ -95,7 +119,7 @@ public class PayNymController extends SorobanController {
|
||||
findPayNym.setVisible(false);
|
||||
|
||||
followingList.setCellFactory(param -> {
|
||||
return new PayNymCell(walletId);
|
||||
return new PayNymCell(this);
|
||||
});
|
||||
|
||||
followingList.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, payNym) -> {
|
||||
@@ -103,13 +127,17 @@ public class PayNymController extends SorobanController {
|
||||
});
|
||||
|
||||
followersList.setCellFactory(param -> {
|
||||
return new PayNymCell(walletId);
|
||||
return new PayNymCell(null);
|
||||
});
|
||||
|
||||
followersList.setSelectionModel(new NoSelectionModel<>());
|
||||
followersList.setFocusTraversable(false);
|
||||
|
||||
refresh();
|
||||
if(Config.get().isUsePayNym() && soroban.getPaymentCode() != null) {
|
||||
refresh();
|
||||
} else {
|
||||
payNymName.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
@@ -124,8 +152,14 @@ public class PayNymController extends SorobanController {
|
||||
paymentCode.setPaymentCode(payNym.paymentCode());
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
followingList.setUserData(null);
|
||||
followingList.setPlaceholder(new Label("No contacts"));
|
||||
followingList.setItems(FXCollections.observableList(payNym.following()));
|
||||
followersList.setPlaceholder(new Label("No followers"));
|
||||
followersList.setItems(FXCollections.observableList(payNym.followers()));
|
||||
}, error -> {
|
||||
if(error.getMessage().endsWith("404")) {
|
||||
payNymName.setVisible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -180,6 +214,98 @@ public class PayNymController extends SorobanController {
|
||||
}
|
||||
}
|
||||
|
||||
public void retrievePayNym(ActionEvent event) {
|
||||
Config.get().setUsePayNym(true);
|
||||
makeAuthenticatedCall(null);
|
||||
}
|
||||
|
||||
public void followPayNym(PaymentCode paymentCode) {
|
||||
makeAuthenticatedCall(paymentCode);
|
||||
}
|
||||
|
||||
private void makeAuthenticatedCall(PaymentCode contact) {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
if(soroban.getHdWallet() == null) {
|
||||
Wallet wallet = AppServices.get().getWallet(walletId);
|
||||
if(wallet.isEncrypted()) {
|
||||
Wallet copy = wallet.copy();
|
||||
WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||
Optional<SecureString> password = dlg.showAndWait();
|
||||
if(password.isPresent()) {
|
||||
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
||||
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(storage, password.get(), true);
|
||||
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
||||
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||
Key key = new Key(encryptionFullKey.getPrivKeyBytes(), storage.getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
|
||||
copy.decrypt(key);
|
||||
|
||||
try {
|
||||
soroban.setHDWallet(copy);
|
||||
makeAuthenticatedCall(soroban, contact);
|
||||
} finally {
|
||||
key.clear();
|
||||
encryptionFullKey.clear();
|
||||
password.get().clear();
|
||||
}
|
||||
});
|
||||
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
||||
if(keyDerivationService.getException() instanceof InvalidPasswordException) {
|
||||
Optional<ButtonType> optResponse = showErrorDialog("Invalid Password", "The wallet password was invalid. Try again?", ButtonType.CANCEL, ButtonType.OK);
|
||||
if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) {
|
||||
Platform.runLater(() -> makeAuthenticatedCall(contact));
|
||||
}
|
||||
} else {
|
||||
log.error("Error deriving wallet key", keyDerivationService.getException());
|
||||
}
|
||||
});
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
|
||||
keyDerivationService.start();
|
||||
}
|
||||
} else {
|
||||
soroban.setHDWallet(wallet);
|
||||
makeAuthenticatedCall(soroban, contact);
|
||||
}
|
||||
} else {
|
||||
makeAuthenticatedCall(soroban, contact);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeAuthenticatedCall(Soroban soroban, PaymentCode contact) {
|
||||
if(contact != null) {
|
||||
followPayNym(soroban, contact);
|
||||
} else {
|
||||
retrievePayNym(soroban);
|
||||
}
|
||||
}
|
||||
|
||||
private void retrievePayNym(Soroban soroban) {
|
||||
soroban.createPayNym().subscribe(createMap -> {
|
||||
payNymName.setText((String)createMap.get("nymName"));
|
||||
payNymAvatar.setPaymentCode(soroban.getPaymentCode());
|
||||
payNymName.setVisible(true);
|
||||
|
||||
claimPayNym(soroban, createMap);
|
||||
refresh();
|
||||
}, error -> {
|
||||
log.error("Error retrieving PayNym", error);
|
||||
AppServices.showErrorDialog("Error retrieving PayNym", error.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
private void followPayNym(Soroban soroban, PaymentCode contact) {
|
||||
soroban.getAuthToken(new HashMap<>()).subscribe(authToken -> {
|
||||
String signature = soroban.getSignature(authToken);
|
||||
soroban.followPaymentCode(contact, authToken, signature).subscribe(followMap -> {
|
||||
refresh();
|
||||
}, error -> {
|
||||
log.error("Could not follow payment code", error);
|
||||
AppServices.showErrorDialog("Could not follow payment code", error.getMessage());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public PayNym getPayNym() {
|
||||
return payNymProperty.get();
|
||||
}
|
||||
@@ -188,22 +314,6 @@ public class PayNymController extends SorobanController {
|
||||
return payNymProperty;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void followPayNym(FollowPayNymEvent event) {
|
||||
if(event.getWalletId().equals(walletId)) {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
soroban.getAuthToken(new HashMap<>()).subscribe(authToken -> {
|
||||
String signature = soroban.getSignature(authToken);
|
||||
soroban.followPaymentCode(event.getPaymentCode(), authToken, signature).subscribe(followMap -> {
|
||||
refresh();
|
||||
}, error -> {
|
||||
log.error("Could not follow payment code", error);
|
||||
AppServices.showErrorDialog("Could not follow payment code", error.getMessage());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class NoSelectionModel<T> extends MultipleSelectionModel<T> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.sparrowwallet.sparrow.soroban;
|
||||
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.*;
|
||||
|
||||
@@ -18,7 +17,6 @@ public class PayNymDialog extends Dialog<PayNym> {
|
||||
dialogPane.setContent(payNymLoader.load());
|
||||
PayNymController payNymController = payNymLoader.getController();
|
||||
payNymController.initializeView(walletId);
|
||||
EventManager.get().register(payNymController);
|
||||
|
||||
dialogPane.setPrefWidth(730);
|
||||
dialogPane.setPrefHeight(600);
|
||||
@@ -27,7 +25,7 @@ public class PayNymDialog extends Dialog<PayNym> {
|
||||
dialogPane.getStylesheets().add(AppServices.class.getResource("app.css").toExternalForm());
|
||||
dialogPane.getStylesheets().add(AppServices.class.getResource("soroban/paynym.css").toExternalForm());
|
||||
|
||||
final ButtonType selectButtonType = new javafx.scene.control.ButtonType("Select PayNym", ButtonBar.ButtonData.APPLY);
|
||||
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);
|
||||
|
||||
@@ -43,10 +41,6 @@ public class PayNymDialog extends Dialog<PayNym> {
|
||||
dialogPane.getButtonTypes().add(doneButtonType);
|
||||
}
|
||||
|
||||
setOnCloseRequest(event -> {
|
||||
EventManager.get().unregister(payNymController);
|
||||
});
|
||||
|
||||
setResultConverter(dialogButton -> dialogButton == selectButtonType ? payNymController.getPayNym() : null);
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -23,6 +23,32 @@ public class SorobanController {
|
||||
private static final Logger log = LoggerFactory.getLogger(SorobanController.class);
|
||||
protected static final Pattern PAYNYM_REGEX = Pattern.compile("\\+[a-z]+[0-9][0-9a-fA-F][0-9a-fA-F]");
|
||||
|
||||
protected void claimPayNym(Soroban soroban, Map<String, Object> createMap) {
|
||||
if(createMap.get("claimed") == Boolean.FALSE) {
|
||||
soroban.getAuthToken(createMap).subscribe(authToken -> {
|
||||
String signature = soroban.getSignature(authToken);
|
||||
soroban.claimPayNym(authToken, signature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
soroban.addSamouraiPaymentCode(authToken, signature).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
}, error -> {
|
||||
soroban.getAuthToken(new HashMap<>()).subscribe(newAuthToken -> {
|
||||
String newSignature = soroban.getSignature(newAuthToken);
|
||||
soroban.claimPayNym(newAuthToken, newSignature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
soroban.addSamouraiPaymentCode(newAuthToken, newSignature).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
});
|
||||
}, newError -> {
|
||||
log.error("Error claiming PayNym", newError);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected Transaction getTransaction(Cahoots cahoots) throws PSBTParseException {
|
||||
if(cahoots.getPSBT() != null) {
|
||||
PSBT psbt = new PSBT(cahoots.getPSBT().toBytes());
|
||||
|
||||
@@ -20,9 +20,15 @@ import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent;
|
||||
import com.sparrowwallet.sparrow.event.OpenWalletsEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNym;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNymAddress;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNymDialog;
|
||||
import com.sparrowwallet.sparrow.soroban.SorobanServices;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -117,6 +123,15 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||
}
|
||||
};
|
||||
|
||||
private final ObjectProperty<PayNym> payNymProperty = new SimpleObjectProperty<>();
|
||||
|
||||
private static final Wallet payNymWallet = new Wallet() {
|
||||
@Override
|
||||
public String getFullDisplayName() {
|
||||
return "PayNym...";
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
EventManager.get().register(this);
|
||||
@@ -140,17 +155,38 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||
return null;
|
||||
}
|
||||
});
|
||||
openWallets.setItems(FXCollections.observableList(AppServices.get().getOpenWallets().keySet().stream().filter(wallet -> wallet.isValid() && !wallet.isWhirlpoolChildWallet()).collect(Collectors.toList())));
|
||||
updateOpenWallets();
|
||||
openWallets.prefWidthProperty().bind(address.widthProperty());
|
||||
openWallets.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null) {
|
||||
if(newValue == payNymWallet) {
|
||||
PayNymDialog payNymDialog = new PayNymDialog(sendController.getWalletForm().getWalletId(), true);
|
||||
Optional<PayNym> optPayNym = payNymDialog.showAndWait();
|
||||
if(optPayNym.isPresent()) {
|
||||
PayNym payNym = optPayNym.get();
|
||||
payNymProperty.set(payNym);
|
||||
address.setText(payNym.nymName());
|
||||
label.requestFocus();
|
||||
}
|
||||
} else if(newValue != null) {
|
||||
WalletNode freshNode = newValue.getFreshNode(KeyPurpose.RECEIVE);
|
||||
Address freshAddress = newValue.getAddress(freshNode);
|
||||
address.setText(freshAddress.toString());
|
||||
label.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
payNymProperty.addListener((observable, oldValue, newValue) -> {
|
||||
addPaymentButton.setDisable(newValue != null);
|
||||
if(newValue != null) {
|
||||
sendController.setPayNymPayment();
|
||||
}
|
||||
});
|
||||
|
||||
address.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) {
|
||||
payNymProperty.set(null);
|
||||
}
|
||||
|
||||
try {
|
||||
BitcoinURI bitcoinURI = new BitcoinURI(newValue);
|
||||
Platform.runLater(() -> updateFromURI(bitcoinURI));
|
||||
@@ -212,6 +248,20 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||
addValidation(validationSupport);
|
||||
}
|
||||
|
||||
private void updateOpenWallets() {
|
||||
updateOpenWallets(AppServices.get().getOpenWallets().keySet());
|
||||
}
|
||||
|
||||
private void updateOpenWallets(Collection<Wallet> wallets) {
|
||||
List<Wallet> openWalletList = wallets.stream().filter(wallet -> wallet.isValid() && !wallet.isWhirlpoolChildWallet()).collect(Collectors.toList());
|
||||
|
||||
if(sendController.getPaymentTabs().getTabs().size() <= 1 && SorobanServices.canWalletMix(sendController.getWalletForm().getWallet())) {
|
||||
openWalletList.add(payNymWallet);
|
||||
}
|
||||
|
||||
openWallets.setItems(FXCollections.observableList(openWalletList));
|
||||
}
|
||||
|
||||
private void addValidation(ValidationSupport validationSupport) {
|
||||
this.validationSupport = validationSupport;
|
||||
|
||||
@@ -245,7 +295,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||
}
|
||||
|
||||
private Address getRecipientAddress() throws InvalidAddressException {
|
||||
return Address.fromString(address.getText());
|
||||
return payNymProperty.get() == null ? Address.fromString(address.getText()) : new PayNymAddress(payNymProperty.get());
|
||||
}
|
||||
|
||||
private Long getRecipientValueSats() {
|
||||
@@ -365,6 +415,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||
setSendMax(false);
|
||||
|
||||
dustAmountProperty.set(false);
|
||||
payNymProperty.set(null);
|
||||
}
|
||||
|
||||
public void setMaxInput(ActionEvent event) {
|
||||
@@ -475,6 +526,6 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||
|
||||
@Subscribe
|
||||
public void openWallets(OpenWalletsEvent event) {
|
||||
openWallets.setItems(FXCollections.observableList(event.getWallets().stream().filter(wallet -> wallet.isValid() && !wallet.isWhirlpoolChildWallet()).collect(Collectors.toList())));
|
||||
updateOpenWallets(event.getWallets());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.net.*;
|
||||
import com.sparrowwallet.sparrow.soroban.InitiatorDialog;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNymAddress;
|
||||
import com.sparrowwallet.sparrow.soroban.SorobanServices;
|
||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||
import javafx.animation.KeyFrame;
|
||||
@@ -397,7 +398,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
|
||||
transactionDiagram.update(walletTransaction);
|
||||
updatePrivacyAnalysis(walletTransaction);
|
||||
createButton.setDisable(walletTransaction == null || isInsufficientFeeRate());
|
||||
createButton.setDisable(walletTransaction == null || isInsufficientFeeRate() || isPayNymPayment(walletTransaction.getPayments()));
|
||||
});
|
||||
|
||||
transactionDiagram.sceneProperty().addListener((observable, oldScene, newScene) -> {
|
||||
@@ -608,7 +609,8 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
OptimizationStrategy optimizationStrategy = (OptimizationStrategy)optimizationToggleGroup.getSelectedToggle().getUserData();
|
||||
if(optimizationStrategy == OptimizationStrategy.PRIVACY
|
||||
&& payments.size() == 1
|
||||
&& (payments.get(0).getAddress().getScriptType() == getWalletForm().getWallet().getAddress(getWalletForm().wallet.getFreshNode(KeyPurpose.RECEIVE)).getScriptType())) {
|
||||
&& (payments.get(0).getAddress().getScriptType() == getWalletForm().getWallet().getAddress(getWalletForm().wallet.getFreshNode(KeyPurpose.RECEIVE)).getScriptType())
|
||||
&& !(payments.get(0).getAddress() instanceof PayNymAddress)) {
|
||||
selectors.add(new StonewallUtxoSelector(noInputsFee));
|
||||
}
|
||||
|
||||
@@ -953,6 +955,17 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPayNymPayment(List<Payment> payments) {
|
||||
return payments.size() == 1 && payments.get(0).getAddress() instanceof PayNymAddress;
|
||||
}
|
||||
|
||||
public void setPayNymPayment() {
|
||||
optimizationToggleGroup.selectToggle(privacyToggle);
|
||||
transactionDiagram.setOptimizationStrategy(OptimizationStrategy.PRIVACY);
|
||||
efficiencyToggle.setDisable(true);
|
||||
privacyToggle.setDisable(false);
|
||||
}
|
||||
|
||||
private boolean isMixPossible(List<Payment> payments) {
|
||||
return (utxoSelectorProperty.get() == null || SorobanServices.canWalletMix(walletForm.getWallet()))
|
||||
&& payments.size() == 1
|
||||
@@ -960,11 +973,16 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
}
|
||||
|
||||
private void updateOptimizationButtons(List<Payment> payments) {
|
||||
if(isMixPossible(payments)) {
|
||||
if(isPayNymPayment(payments)) {
|
||||
setPayNymPayment();
|
||||
} else if(isMixPossible(payments)) {
|
||||
setPreferredOptimizationStrategy();
|
||||
efficiencyToggle.setDisable(false);
|
||||
privacyToggle.setDisable(false);
|
||||
} else {
|
||||
optimizationToggleGroup.selectToggle(efficiencyToggle);
|
||||
transactionDiagram.setOptimizationStrategy(OptimizationStrategy.EFFICIENCY);
|
||||
efficiencyToggle.setDisable(false);
|
||||
privacyToggle.setDisable(true);
|
||||
}
|
||||
}
|
||||
@@ -1033,6 +1051,9 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
|
||||
setInputFieldsDisabled(false);
|
||||
|
||||
efficiencyToggle.setDisable(false);
|
||||
privacyToggle.setDisable(false);
|
||||
|
||||
premixButton.setVisible(false);
|
||||
createButton.setDefaultButton(true);
|
||||
}
|
||||
@@ -1088,23 +1109,24 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
}
|
||||
|
||||
public void createTransaction(ActionEvent event) {
|
||||
WalletTransaction walletTransaction = walletTransactionProperty.get();
|
||||
if(log.isDebugEnabled()) {
|
||||
Map<WalletNode, List<String>> inputHashes = new LinkedHashMap<>();
|
||||
for(WalletNode node : walletTransactionProperty.get().getSelectedUtxos().values()) {
|
||||
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 : walletTransactionProperty.get().getChangeMap().keySet()) {
|
||||
for(WalletNode changeNode : walletTransaction.getChangeMap().keySet()) {
|
||||
changeHash.put(changeNode, List.of(ElectrumServer.getScriptHash(walletForm.getWallet(), changeNode)));
|
||||
}
|
||||
log.debug("Creating tx " + walletTransactionProperty.get().getTransaction().getTxId() + ", expecting notifications for \ninputs \n" + inputHashes + " and \nchange \n" + changeHash);
|
||||
log.debug("Creating tx " + walletTransaction.getTransaction().getTxId() + ", expecting notifications for \ninputs \n" + inputHashes + " and \nchange \n" + changeHash);
|
||||
}
|
||||
|
||||
addWalletTransactionNodes();
|
||||
createdWalletTransactionProperty.set(walletTransactionProperty.get());
|
||||
PSBT psbt = walletTransactionProperty.get().createPSBT();
|
||||
EventManager.get().post(new ViewPSBTEvent(createButton.getScene().getWindow(), walletTransactionProperty.get().getPayments().get(0).getLabel(), null, psbt));
|
||||
createdWalletTransactionProperty.set(walletTransaction);
|
||||
PSBT psbt = walletTransaction.createPSBT();
|
||||
EventManager.get().post(new ViewPSBTEvent(createButton.getScene().getWindow(), walletTransaction.getPayments().get(0).getLabel(), null, psbt));
|
||||
}
|
||||
|
||||
private void addWalletTransactionNodes() {
|
||||
@@ -1379,7 +1401,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
public void sorobanInitiated(SorobanInitiatedEvent event) {
|
||||
if(event.getWallet().equals(getWalletForm().getWallet())) {
|
||||
InitiatorDialog initiatorDialog = new InitiatorDialog(getWalletForm().getWalletId(), getWalletForm().getWallet(), walletTransactionProperty.get());
|
||||
if(Network.get() == Network.TESTNET) {
|
||||
if(Config.get().isSameAppMixing()) {
|
||||
initiatorDialog.initModality(Modality.NONE);
|
||||
}
|
||||
Optional<Transaction> optTransaction = initiatorDialog.showAndWait();
|
||||
@@ -1414,13 +1436,16 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
List<Payment> payments = walletTransaction.getPayments();
|
||||
List<Payment> userPayments = payments.stream().filter(payment -> payment.getType() != Payment.Type.FAKE_MIX).collect(Collectors.toList());
|
||||
OptimizationStrategy optimizationStrategy = getPreferredOptimizationStrategy();
|
||||
boolean payNymPresent = isPayNymPayment(payments);
|
||||
boolean fakeMixPresent = payments.stream().anyMatch(payment -> payment.getType() == Payment.Type.FAKE_MIX);
|
||||
boolean roundPaymentAmounts = userPayments.stream().anyMatch(payment -> payment.getAmount() % 100 == 0);
|
||||
boolean mixedAddressTypes = userPayments.stream().anyMatch(payment -> payment.getAddress().getScriptType() != getWalletForm().getWallet().getAddress(getWalletForm().wallet.getFreshNode(KeyPurpose.RECEIVE)).getScriptType());
|
||||
boolean addressReuse = userPayments.stream().anyMatch(payment -> getWalletForm().getWallet().getWalletAddresses().get(payment.getAddress()) != null && !getWalletForm().getWallet().getWalletAddresses().get(payment.getAddress()).getTransactionOutputs().isEmpty());
|
||||
|
||||
if(optimizationStrategy == OptimizationStrategy.PRIVACY) {
|
||||
if(fakeMixPresent) {
|
||||
if(payNymPresent) {
|
||||
addLabel("Appears as a normal transaction, but actual value transferred is hidden", getPlusGlyph());
|
||||
} else if(fakeMixPresent) {
|
||||
addLabel("Appears as a two person coinjoin", getPlusGlyph());
|
||||
} else {
|
||||
if(mixedAddressTypes) {
|
||||
@@ -1447,7 +1472,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
addLabel("Address types different to the wallet indicate external payments", getMinusGlyph());
|
||||
}
|
||||
|
||||
if(roundPaymentAmounts && !fakeMixPresent) {
|
||||
if(roundPaymentAmounts && !fakeMixPresent && !payNymPresent) {
|
||||
addLabel("Rounded payment amounts indicate external payments", getMinusGlyph());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user