mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-07-31 12:06:15 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81eda96690 | ||
|
|
af9eb3cc64 | ||
|
|
d9bba16eb6 | ||
|
|
58cd50f674 | ||
|
|
b2b9dbeb8d | ||
|
|
0469141fee | ||
|
|
b16c7345a8 | ||
|
|
b1940e9293 | ||
|
|
1f51f632c4 | ||
|
|
ba199ff11b | ||
|
|
79c0f7769a | ||
|
|
761ec0659f | ||
|
|
5e31cdb7ac | ||
|
|
468384d82a | ||
|
|
230a4c5585 | ||
|
|
9048d341c6 | ||
|
|
b0f60bb671 |
+2
-1
@@ -5,7 +5,7 @@ plugins {
|
||||
id 'org.beryx.jlink' version '2.24.0'
|
||||
}
|
||||
|
||||
def sparrowVersion = '1.6.2'
|
||||
def sparrowVersion = '1.6.3'
|
||||
def os = org.gradle.internal.os.OperatingSystem.current()
|
||||
def osName = os.getFamilyName()
|
||||
if(os.macOsX) {
|
||||
@@ -97,6 +97,7 @@ dependencies {
|
||||
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
||||
implementation('org.apache.commons:commons-lang3:3.7')
|
||||
implementation('net.sourceforge.streamsupport:streamsupport:1.7.0')
|
||||
implementation('com.github.librepdf:openpdf:1.3.27')
|
||||
testImplementation('junit:junit:4.12')
|
||||
}
|
||||
|
||||
|
||||
+1
-1
Submodule drongo updated: fe57ad3e7a...20f4ac9657
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.6.2</string>
|
||||
<string>1.6.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||
|
||||
@@ -196,6 +196,8 @@ public class AppController implements Initializable {
|
||||
|
||||
private Tab previouslySelectedTab;
|
||||
|
||||
private boolean subTabsVisible;
|
||||
|
||||
private final Set<Wallet> loadingWallets = new LinkedHashSet<>();
|
||||
|
||||
private final Set<Wallet> emptyLoadingWallets = new LinkedHashSet<>();
|
||||
@@ -891,6 +893,7 @@ public class AppController implements Initializable {
|
||||
Storage storage = new Storage(file);
|
||||
if(!storage.isEncrypted()) {
|
||||
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage);
|
||||
loadWalletService.setExecutor(Storage.LoadWalletService.getSingleThreadedExecutor());
|
||||
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
||||
WalletAndKey walletAndKey = loadWalletService.getValue();
|
||||
openWallet(storage, walletAndKey, this, forceSameWindow);
|
||||
@@ -1458,8 +1461,8 @@ public class AppController implements Initializable {
|
||||
});
|
||||
|
||||
TabPane subTabs = new TabPane();
|
||||
subTabs.setSide(Side.RIGHT);
|
||||
setSubTabsVisible(subTabs, false);
|
||||
subTabs.setSide(Side.LEFT);
|
||||
setSubTabsVisible(subTabs, areSubTabsVisible());
|
||||
subTabs.rotateGraphicProperty().set(true);
|
||||
tab.setContent(subTabs);
|
||||
|
||||
@@ -1468,7 +1471,7 @@ public class AppController implements Initializable {
|
||||
tab.setUserData(tabData);
|
||||
tab.setContextMenu(getTabContextMenu(tab));
|
||||
walletForm.lockedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
setSubTabsVisible(subTabs, !newValue && subTabs.getTabs().size() > 1);
|
||||
setSubTabsVisible(subTabs, !newValue && areSubTabsVisible());
|
||||
});
|
||||
|
||||
subTabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> {
|
||||
@@ -1527,6 +1530,30 @@ public class AppController implements Initializable {
|
||||
}
|
||||
}
|
||||
|
||||
private void setSubTabsVisible(boolean visible) {
|
||||
for(Tab tab : tabs.getTabs()) {
|
||||
TabData tabData = (TabData) tab.getUserData();
|
||||
if(tabData instanceof WalletTabData) {
|
||||
setSubTabsVisible((TabPane)tab.getContent(), visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean areSubTabsVisible() {
|
||||
if(subTabsVisible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for(Wallet wallet : AppServices.get().getOpenWallets().keySet()) {
|
||||
if(wallet.getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) {
|
||||
subTabsVisible = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet wallet) {
|
||||
try {
|
||||
Tab subTab = new Tab();
|
||||
@@ -2018,6 +2045,18 @@ public class AppController implements Initializable {
|
||||
findMixingPartner.setDisable(exportWallet.isDisable() || !SorobanServices.canWalletMix(event.getWallet()) || !AppServices.onlineProperty().get());
|
||||
}
|
||||
}
|
||||
|
||||
for(Tab walletTab : tabs.getTabs()) {
|
||||
TabData tabData = (TabData) walletTab.getUserData();
|
||||
if(tabData instanceof WalletTabData walletTabData) {
|
||||
if(walletTabData.getWalletForm().getWalletId().equals(event.getWalletId()) && event.getWallet().isMasterWallet()) {
|
||||
TabPane subTabs = (TabPane)walletTab.getContent();
|
||||
Tab masterTab = subTabs.getTabs().stream().filter(tab -> ((WalletTabData)tab.getUserData()).getWallet().isMasterWallet()).findFirst().orElse(subTabs.getTabs().get(0));
|
||||
Label masterLabel = (Label)masterTab.getGraphic();
|
||||
masterLabel.setText(event.getWallet().getLabel() != null ? event.getWallet().getLabel() : event.getWallet().getAutomaticName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -2252,6 +2291,14 @@ public class AppController implements Initializable {
|
||||
serverToggleStopAnimation();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletOpened(WalletOpenedEvent walletOpenedEvent) {
|
||||
if(!subTabsVisible && walletOpenedEvent.getWallet().getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) {
|
||||
subTabsVisible = true;
|
||||
setSubTabsVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletTabsClosed(WalletTabsClosedEvent event) {
|
||||
event.getClosedWalletTabData().stream().map(WalletTabData::getWallet).forEach(loadingWallets::remove);
|
||||
|
||||
@@ -292,9 +292,12 @@ public class AppServices {
|
||||
"\n\nThis may indicate a man-in-the-middle attack!" +
|
||||
"\n\nDo you still want to proceed?", ButtonType.NO, ButtonType.YES);
|
||||
if(optButton.isPresent() && optButton.get() == ButtonType.YES) {
|
||||
crtFile.delete();
|
||||
Platform.runLater(() -> restartService(connectionService));
|
||||
return;
|
||||
if(crtFile.delete()) {
|
||||
Platform.runLater(() -> restartService(connectionService));
|
||||
return;
|
||||
} else {
|
||||
AppServices.showErrorDialog("Could not delete certificate", "The certificate file at " + crtFile.getAbsolutePath() + " could not be deleted.\n\nPlease delete this file manually.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.stream.Collectors;
|
||||
public class MainApp extends Application {
|
||||
public static final String APP_ID = "com.sparrowwallet.sparrow";
|
||||
public static final String APP_NAME = "Sparrow";
|
||||
public static final String APP_VERSION = "1.6.2";
|
||||
public static final String APP_VERSION = "1.6.3";
|
||||
public static final String APP_VERSION_SUFFIX = "";
|
||||
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
||||
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
||||
|
||||
@@ -4,8 +4,8 @@ import com.sparrowwallet.drongo.BitcoinUnit;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletDataChangedEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletHistoryClearedEvent;
|
||||
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
@@ -91,7 +91,7 @@ public class CoinTreeTable extends TreeTableView<Entry> {
|
||||
EventManager.get().post(new WalletDataChangedEvent(wallet));
|
||||
//Trigger full wallet rescan
|
||||
wallet.clearHistory();
|
||||
EventManager.get().post(new WalletHistoryClearedEvent(wallet, pastWallet, storage.getWalletId(wallet)));
|
||||
EventManager.get().post(new WalletAddressesChangedEvent(wallet, pastWallet, storage.getWalletId(wallet)));
|
||||
}
|
||||
});
|
||||
if(wallet.getBirthDate() == null) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.lowagie.text.*;
|
||||
import com.lowagie.text.Font;
|
||||
import com.lowagie.text.Image;
|
||||
import com.lowagie.text.pdf.PdfWriter;
|
||||
import com.sparrowwallet.hummingbird.UR;
|
||||
import com.sparrowwallet.hummingbird.UREncoder;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class DescriptorQRDisplayDialog extends QRDisplayDialog {
|
||||
private static final Logger log = LoggerFactory.getLogger(DescriptorQRDisplayDialog.class);
|
||||
|
||||
public DescriptorQRDisplayDialog(String walletName, String outputDescriptor, UR ur) {
|
||||
super(ur);
|
||||
|
||||
DialogPane dialogPane = getDialogPane();
|
||||
final ButtonType pdfButtonType = new javafx.scene.control.ButtonType("Save PDF...", ButtonBar.ButtonData.HELP_2);
|
||||
dialogPane.getButtonTypes().add(pdfButtonType);
|
||||
|
||||
Button pdfButton = (Button)dialogPane.lookupButton(pdfButtonType);
|
||||
pdfButton.addEventFilter(ActionEvent.ACTION, event -> {
|
||||
savePdf(walletName, outputDescriptor, ur);
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
private void savePdf(String walletName, String outputDescriptor, UR ur) {
|
||||
Stage window = new Stage();
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Save PDF");
|
||||
fileChooser.setInitialFileName(walletName + ".pdf");
|
||||
AppServices.moveToActiveWindowScreen(window, 800, 450);
|
||||
File file = fileChooser.showSaveDialog(window);
|
||||
if(file != null) {
|
||||
try(Document document = new Document()) {
|
||||
document.setMargins(36, 36, 48, 36);
|
||||
PdfWriter.getInstance(document, new FileOutputStream(file));
|
||||
document.open();
|
||||
|
||||
Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16, Color.BLACK);
|
||||
Chunk title = new Chunk("Output descriptor for " + walletName, titleFont);
|
||||
document.add(title);
|
||||
|
||||
UREncoder urEncoder = new UREncoder(ur, 2000, 10, 0);
|
||||
String fragment = urEncoder.nextPart();
|
||||
if(urEncoder.isSinglePart()) {
|
||||
Image image = Image.getInstance(SwingFXUtils.fromFXImage(getQrCode(fragment), null), Color.WHITE);
|
||||
document.add(image);
|
||||
}
|
||||
|
||||
Font descriptorFont = FontFactory.getFont(FontFactory.COURIER, 14, Color.BLACK);
|
||||
Paragraph descriptor = new Paragraph(outputDescriptor, descriptorFont);
|
||||
document.add(descriptor);
|
||||
} catch(Exception e) {
|
||||
log.error("Error creating descriptor PDF", e);
|
||||
AppServices.showErrorDialog("Error creating PDF", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public class HelpLabel extends Label {
|
||||
tooltip.textProperty().bind(helpTextProperty());
|
||||
tooltip.graphicProperty().bind(helpGraphicProperty());
|
||||
tooltip.setShowDuration(Duration.seconds(15));
|
||||
tooltip.setShowDelay(Duration.millis(500));
|
||||
getStyleClass().add("help-label");
|
||||
|
||||
Platform.runLater(() -> setTooltip(tooltip));
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.control;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymService;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.Service;
|
||||
@@ -117,8 +118,8 @@ public class PayNymAvatar extends StackPane {
|
||||
}
|
||||
|
||||
synchronized(lock) {
|
||||
String url = "https://paynym.is/" + paymentCodeStr + "/avatar";
|
||||
Proxy proxy = AppServices.getProxy();
|
||||
String url = PayNymService.getHostUrl(proxy != null) + "/" + paymentCodeStr + "/avatar";
|
||||
|
||||
try(InputStream is = (proxy == null ? new URL(url).openStream() : new URL(url).openConnection(proxy).getInputStream())) {
|
||||
Image image = new Image(is, 150, 150, true, false);
|
||||
|
||||
@@ -137,7 +137,7 @@ public class QRDisplayDialog extends Dialog<UR> {
|
||||
}
|
||||
}
|
||||
|
||||
private Image getQrCode(String fragment) {
|
||||
protected Image getQrCode(String fragment) {
|
||||
try {
|
||||
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||
BitMatrix qrMatrix = qrCodeWriter.encode(fragment, BarcodeFormat.QR_CODE, QR_WIDTH, QR_HEIGHT);
|
||||
|
||||
@@ -57,6 +57,7 @@ public class UtxosChart extends BarChart<String, Number> {
|
||||
|
||||
for(int i = 0; i < utxoDataList.size(); i++) {
|
||||
XYChart.Data<String, Number> newData = utxoDataList.get(i);
|
||||
newData.setXValue((i+1) + ". " + newData.getXValue());
|
||||
if(i < utxoSeries.getData().size()) {
|
||||
XYChart.Data<String, Number> existingData = utxoSeries.getData().get(i);
|
||||
if(!newData.getXValue().equals(existingData.getXValue()) || !newData.getYValue().equals(existingData.getYValue()) || (newData.getExtraValue() instanceof Entry && !newData.getExtraValue().equals(existingData.getExtraValue()))) {
|
||||
@@ -70,7 +71,7 @@ public class UtxosChart extends BarChart<String, Number> {
|
||||
}
|
||||
|
||||
if(utxoSeries.getData().size() > utxoDataList.size()) {
|
||||
utxoSeries.getData().remove(Math.max(0, utxoDataList.size() - 1), utxoSeries.getData().size());
|
||||
utxoSeries.getData().remove(utxoDataList.size(), utxoSeries.getData().size());
|
||||
}
|
||||
|
||||
if(selectedEntries != null) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
|
||||
public class TransactionFetchFailedEvent extends TransactionReferencesFailedEvent {
|
||||
public TransactionFetchFailedEvent(Transaction transaction, Throwable exception) {
|
||||
super(transaction, exception);
|
||||
}
|
||||
|
||||
public TransactionFetchFailedEvent(Transaction transaction, Throwable exception, int pageStart, int pageEnd) {
|
||||
super(transaction, exception, pageStart, pageEnd);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.MainApp;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.controlsfx.tools.Platform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -22,6 +23,8 @@ import java.security.cert.CertificateEncodingException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -509,6 +512,8 @@ public class Storage {
|
||||
private final Storage storage;
|
||||
private final SecureString password;
|
||||
|
||||
private static Executor singleThreadedExecutor;
|
||||
|
||||
public LoadWalletService(Storage storage) {
|
||||
this.storage = storage;
|
||||
this.password = null;
|
||||
@@ -536,6 +541,15 @@ public class Storage {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Executor getSingleThreadedExecutor() {
|
||||
if(singleThreadedExecutor == null) {
|
||||
BasicThreadFactory factory = new BasicThreadFactory.Builder().namingPattern("LoadWalletService-single").daemon(true).priority(Thread.MIN_PRIORITY).build();
|
||||
singleThreadedExecutor = Executors.newSingleThreadScheduledExecutor(factory);
|
||||
}
|
||||
|
||||
return singleThreadedExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyDerivationService extends Service<ECKey> {
|
||||
|
||||
@@ -348,7 +348,7 @@ public class ElectrumServer {
|
||||
|
||||
private Set<WalletNode> getAddressNodes(Wallet wallet, WalletNode purposeNode) {
|
||||
Integer watchLast = wallet.getWatchLast();
|
||||
if(watchLast == null || watchLast < wallet.getGapLimit() || wallet.getStoredBlockHeight() == 0 || wallet.getTransactions().isEmpty()) {
|
||||
if(watchLast == null || watchLast < wallet.getGapLimit() || wallet.getStoredBlockHeight() == null || wallet.getStoredBlockHeight() == 0 || wallet.getTransactions().isEmpty()) {
|
||||
return purposeNode.getChildren();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class PayNymService {
|
||||
body.put("code", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/create", Map.class, headers, body)
|
||||
return httpClient.postJson(getHostUrl() + "/api/v1/create", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
@@ -70,7 +70,7 @@ public class PayNymService {
|
||||
body.put("code", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/token", Map.class, headers, body)
|
||||
return httpClient.postJson(getHostUrl() + "/api/v1/token", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
@@ -115,7 +115,7 @@ public class PayNymService {
|
||||
body.put("signature", signature);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/claim", Map.class, headers, body)
|
||||
return httpClient.postJson(getHostUrl() + "/api/v1/claim", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
@@ -140,7 +140,7 @@ public class PayNymService {
|
||||
body.put("signature", signature);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/nym/add", Map.class, headers, body)
|
||||
return httpClient.postJson(getHostUrl() + "/api/v1/nym/add", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
@@ -160,7 +160,7 @@ public class PayNymService {
|
||||
body.put("target", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/follow", Map.class, headers, body)
|
||||
return httpClient.postJson(getHostUrl() + "/api/v1/follow", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
@@ -174,7 +174,7 @@ public class PayNymService {
|
||||
body.put("nym", nymIdentifier);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/nym", Map.class, headers, body)
|
||||
return httpClient.postJson(getHostUrl() + "/api/v1/nym", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
@@ -235,6 +235,14 @@ public class PayNymService {
|
||||
httpClientService.setTorProxy(torProxy);
|
||||
}
|
||||
|
||||
private String getHostUrl() {
|
||||
return getHostUrl(getTorProxy() != null);
|
||||
}
|
||||
|
||||
public static String getHostUrl(boolean tor) {
|
||||
return tor ? "http://paynym7bwekdtb2hzgkpl6y2waqcrs2dii7lwincvxme7mdpcpxzfsad.onion" : "https://paynym.is";
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
httpClientService.shutdown();
|
||||
}
|
||||
|
||||
+4
-3
@@ -587,11 +587,12 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||
Optional<ButtonType> optButton = AppServices.showErrorDialog("SSL Handshake Failed", "The certificate provided by the server at " + tlsServerException.getServer().getHost() + " appears to have changed." +
|
||||
"\n\nThis may indicate a man-in-the-middle attack!" +
|
||||
"\n\nDo you still want to proceed?", ButtonType.NO, ButtonType.YES);
|
||||
if(optButton.isPresent()) {
|
||||
if(optButton.get() == ButtonType.YES) {
|
||||
savedCrtFile.delete();
|
||||
if(optButton.isPresent() && optButton.get() == ButtonType.YES) {
|
||||
if(savedCrtFile.delete()) {
|
||||
Platform.runLater(this::startElectrumConnection);
|
||||
return;
|
||||
} else {
|
||||
AppServices.showErrorDialog("Could not delete certificate", "The certificate file at " + savedCrtFile.getAbsolutePath() + " could not be deleted.\n\nPlease delete this file manually.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.samourai.wallet.cahoots.CahootsType;
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
||||
import com.sparrowwallet.drongo.wallet.Status;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
@@ -296,7 +297,9 @@ public class CounterpartyController extends SorobanController {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
Map<BlockTransactionHashIndex, WalletNode> walletUtxos = wallet.getWalletUtxos();
|
||||
for(Map.Entry<BlockTransactionHashIndex, WalletNode> entry : walletUtxos.entrySet()) {
|
||||
counterpartyCahootsWallet.addUtxo(entry.getValue(), wallet.getWalletTransaction(entry.getKey().getHash()), (int)entry.getKey().getIndex());
|
||||
if(entry.getKey().getStatus() != Status.FROZEN) {
|
||||
counterpartyCahootsWallet.addUtxo(entry.getValue(), wallet.getWalletTransaction(entry.getKey().getHash()), (int)entry.getKey().getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -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 -> {
|
||||
@@ -433,7 +453,9 @@ public class InitiatorController extends SorobanController {
|
||||
Payment payment = walletTransaction.getPayments().get(0);
|
||||
Map<BlockTransactionHashIndex, WalletNode> firstSetUtxos = walletTransaction.isCoinControlUsed() ? walletTransaction.getSelectedUtxoSets().get(0) : wallet.getWalletUtxos();
|
||||
for(Map.Entry<BlockTransactionHashIndex, WalletNode> entry : firstSetUtxos.entrySet()) {
|
||||
initiatorCahootsWallet.addUtxo(entry.getValue(), wallet.getWalletTransaction(entry.getKey().getHash()), (int)entry.getKey().getIndex());
|
||||
if(entry.getKey().getStatus() != Status.FROZEN) {
|
||||
initiatorCahootsWallet.addUtxo(entry.getValue(), wallet.getWalletTransaction(entry.getKey().getHash()), (int)entry.getKey().getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
SorobanCahootsService sorobanCahootsService = soroban.getSorobanCahootsService(initiatorCahootsWallet);
|
||||
@@ -615,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());
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.*;
|
||||
import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||
import com.sparrowwallet.sparrow.io.Device;
|
||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||
@@ -951,8 +952,13 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
|
||||
private void finalizePSBT() {
|
||||
if(headersForm.getPsbt() != null && headersForm.getPsbt().isSigned() && !headersForm.getPsbt().isFinalized()) {
|
||||
headersForm.getSigningWallet().finalise(headersForm.getPsbt());
|
||||
EventManager.get().post(new PSBTFinalizedEvent(headersForm.getPsbt()));
|
||||
try {
|
||||
headersForm.getSigningWallet().finalise(headersForm.getPsbt());
|
||||
EventManager.get().post(new PSBTFinalizedEvent(headersForm.getPsbt()));
|
||||
} catch(IllegalArgumentException e) {
|
||||
AppServices.showErrorDialog("Cannot finalize PSBT", e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1172,6 +1178,23 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void transactionFetchFailed(TransactionFetchFailedEvent event) {
|
||||
if(event.getTransaction().getTxId().equals(headersForm.getTransaction().getTxId())
|
||||
&& !blockchainForm.isVisible() && !signingWalletForm.isVisible() && !signaturesForm.isVisible()) {
|
||||
blockchainForm.setVisible(true);
|
||||
blockStatus.setText("Unknown transaction status, server failed to respond");
|
||||
Glyph errorGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
|
||||
errorGlyph.setFontSize(12);
|
||||
blockStatus.setGraphic(errorGlyph);
|
||||
blockStatus.setContentDisplay(ContentDisplay.LEFT);
|
||||
errorGlyph.getStyleClass().add("failure");
|
||||
blockHeightField.setVisible(false);
|
||||
blockTimestampField.setVisible(false);
|
||||
blockHashField.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void bitcoinUnitChanged(BitcoinUnitChangedEvent event) {
|
||||
fee.refresh(event.getBitcoinUnit());
|
||||
|
||||
@@ -379,7 +379,11 @@ public class TransactionController implements Initializable {
|
||||
});
|
||||
transactionReferenceService.setOnFailed(failedEvent -> {
|
||||
log.error("Error fetching transaction or input references", failedEvent.getSource().getException());
|
||||
EventManager.get().post(new TransactionReferencesFailedEvent(getTransaction(), failedEvent.getSource().getException(), indexStart, maxIndex));
|
||||
if(references.contains(getTransaction().getTxId())) {
|
||||
EventManager.get().post(new TransactionFetchFailedEvent(getTransaction(), failedEvent.getSource().getException(), indexStart, maxIndex));
|
||||
} else {
|
||||
EventManager.get().post(new TransactionReferencesFailedEvent(getTransaction(), failedEvent.getSource().getException(), indexStart, maxIndex));
|
||||
}
|
||||
});
|
||||
EventManager.get().post(new TransactionReferencesStartedEvent(getTransaction(), indexStart, maxIndex));
|
||||
transactionReferenceService.start();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -324,6 +324,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||
return;
|
||||
}
|
||||
|
||||
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(walletForm.getWallet(), KeyPurpose.DEFAULT_PURPOSES, null);
|
||||
List<ScriptExpression> scriptExpressions = getScriptExpressions(walletForm.getWallet().getScriptType());
|
||||
|
||||
CryptoOutput cryptoOutput;
|
||||
@@ -341,7 +342,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||
}
|
||||
|
||||
UR cryptoOutputUR = cryptoOutput.toUR();
|
||||
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(cryptoOutputUR);
|
||||
QRDisplayDialog qrDisplayDialog = new DescriptorQRDisplayDialog(walletForm.getWallet().getFullDisplayName(), outputDescriptor.toString(true), cryptoOutputUR);
|
||||
qrDisplayDialog.showAndWait();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolDialog;
|
||||
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||
@@ -186,8 +187,8 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||
}
|
||||
|
||||
private void updateFields(WalletUtxosEntry walletUtxosEntry) {
|
||||
balance.setValue(walletUtxosEntry.getChildren().stream().mapToLong(Entry::getValue).sum());
|
||||
mempoolBalance.setValue(walletUtxosEntry.getChildren().stream().filter(entry -> ((UtxoEntry)entry).getHashIndex().getHeight() <= 0).mapToLong(Entry::getValue).sum());
|
||||
balance.setValue(walletUtxosEntry.getBalance());
|
||||
mempoolBalance.setValue(walletUtxosEntry.getMempoolBalance());
|
||||
utxoCount.setText(walletUtxosEntry.getChildren() != null ? Integer.toString(walletUtxosEntry.getChildren().size()) : "0");
|
||||
}
|
||||
|
||||
@@ -612,4 +613,20 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||
selectEntry(utxosTable, utxosTable.getRoot(), event.getEntry());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void fiatCurrencySelected(FiatCurrencySelectedEvent event) {
|
||||
if(event.getExchangeSource() == ExchangeSource.NONE) {
|
||||
fiatBalance.setCurrency(null);
|
||||
fiatBalance.setBtcRate(0.0);
|
||||
fiatMempoolBalance.setCurrency(null);
|
||||
fiatMempoolBalance.setBtcRate(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void exchangeRatesUpdated(ExchangeRatesUpdatedEvent event) {
|
||||
setFiatBalance(fiatBalance, event.getCurrencyRate(), getWalletForm().getWalletUtxosEntry().getBalance());
|
||||
setFiatBalance(fiatMempoolBalance, event.getCurrencyRate(), getWalletForm().getWalletUtxosEntry().getMempoolBalance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,4 +76,12 @@ public class WalletUtxosEntry extends Entry {
|
||||
calculateDuplicates();
|
||||
updateMixProgress();
|
||||
}
|
||||
|
||||
public long getBalance() {
|
||||
return getChildren().stream().mapToLong(Entry::getValue).sum();
|
||||
}
|
||||
|
||||
public long getMempoolBalance() {
|
||||
return getChildren().stream().filter(entry -> ((UtxoEntry)entry).getHashIndex().getHeight() <= 0).mapToLong(Entry::getValue).sum();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,4 +44,5 @@ open module com.sparrowwallet.sparrow {
|
||||
requires org.apache.commons.lang3;
|
||||
requires net.sourceforge.streamsupport;
|
||||
requires co.nstant.in.cbor;
|
||||
requires com.github.librepdf.openpdf;
|
||||
}
|
||||
@@ -34,12 +34,19 @@
|
||||
|
||||
.master-only > .tab-header-area {
|
||||
visibility: hidden;
|
||||
-fx-pref-height: 0;
|
||||
}
|
||||
|
||||
.wallet-subtabs > .tab-header-area .tab {
|
||||
-fx-pref-height: 50;
|
||||
-fx-pref-width: 90;
|
||||
-fx-alignment: CENTER;
|
||||
-fx-background-color: derive(#3da0e3, 32%);
|
||||
-fx-background-insets: 0 1 0 1,0,0;
|
||||
}
|
||||
|
||||
.wallet-subtabs > .tab-header-area .tab:selected {
|
||||
-fx-background-color: #3da0e3;
|
||||
}
|
||||
|
||||
.wallet-subtabs > .tab-header-area .tab-label {
|
||||
@@ -49,6 +56,10 @@
|
||||
-fx-translate-x: -6;
|
||||
}
|
||||
|
||||
.wallet-subtabs > .tab-header-area .tab-label .label {
|
||||
-fx-text-fill: #fff;
|
||||
}
|
||||
|
||||
.status-bar .status-label {
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<FeeRatesSource fx:constant="ELECTRUM_SERVER" />
|
||||
<FeeRatesSource fx:constant="MEMPOOL_SPACE" />
|
||||
<FeeRatesSource fx:constant="BITCOINFEES_EARN_COM" />
|
||||
<FeeRatesSource fx:constant="MINIMUM" />
|
||||
</FXCollections>
|
||||
</items>
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
-fx-background-color: #4aa7e5;
|
||||
-fx-background-color: linear-gradient(to right, #3da0e3, derive(#4aa7e5, 10%));
|
||||
}
|
||||
|
||||
.list-item:selected {
|
||||
-fx-background-color: #1e88cf;
|
||||
-fx-background-color: linear-gradient(to right, #3da0e3, derive(#1e88cf, -10%));
|
||||
}
|
||||
|
||||
.list-item:disabled {
|
||||
|
||||
@@ -41,12 +41,7 @@
|
||||
</graphic>
|
||||
</Label>
|
||||
<HBox>
|
||||
<VBox spacing="15">
|
||||
<Label text="Initiating your first CoinJoin in Sparrow will add three new wallets to your existing wallet: Premix, Postmix and Badbank." wrapText="true" styleClass="content-text" />
|
||||
<Label text="Premix contains UTXOs that have been split from your deposit UTXOs into equal amounts, waiting for their first mixing round. Postmix contains UTXOs that have been through at least one mixing round. Badbank contains any change from your premix transaction, and should be treated carefully." wrapText="true" styleClass="content-text" />
|
||||
<Label text="Click on the tabs at the right of the wallet to use these wallets. Note that they will have reduced functionality (for example they will not display receiving addresses)." wrapText="true" styleClass="content-text" />
|
||||
</VBox>
|
||||
<TabPane side="RIGHT" rotateGraphic="true" styleClass="wallet-subtabs" minWidth="100" minHeight="280">
|
||||
<TabPane side="LEFT" rotateGraphic="true" styleClass="wallet-subtabs" minWidth="100" minHeight="280">
|
||||
<Tab text="" closable="false">
|
||||
<graphic>
|
||||
<Label text="Premix" contentDisplay="TOP">
|
||||
@@ -78,6 +73,11 @@
|
||||
<HBox/>
|
||||
</Tab>
|
||||
</TabPane>
|
||||
<VBox spacing="15">
|
||||
<Label text="Initiating your first CoinJoin in Sparrow will add three new wallets to your existing wallet: Premix, Postmix and Badbank." wrapText="true" styleClass="content-text" />
|
||||
<Label text="Premix contains UTXOs that have been split from your deposit UTXOs into equal amounts, waiting for their first mixing round. Postmix contains UTXOs that have been through at least one mixing round. Badbank contains any change from your premix transaction, and should be treated carefully." wrapText="true" styleClass="content-text" />
|
||||
<Label text="Click on the tabs at the right of the wallet to use these wallets. Note that they will have reduced functionality (for example they will not display receiving addresses)." wrapText="true" styleClass="content-text" />
|
||||
</VBox>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<VBox fx:id="step3" spacing="15">
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user