diff --git a/drongo b/drongo index 2a456dd6..58cc096f 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 2a456dd6027937705be7f6153b5a0a0eea757377 +Subproject commit 58cc096f8e5a1274945a252907100c1dc051a996 diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index dcf42a4b..2e79d8f9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -1233,7 +1233,7 @@ public class AppServices { } public static Font getMonospaceFont() { - return Font.font("Roboto Mono", 13); + return Font.font("Fragment Mono Regular", 13); } public static boolean isOnWayland() { diff --git a/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java b/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java index fafba00c..81ea740c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java +++ b/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java @@ -113,8 +113,8 @@ public class SparrowDesktop extends Application { private void initializeFonts() { GlyphFontRegistry.register(new FontAwesome5()); GlyphFontRegistry.register(new FontAwesome5Brands()); - Font.loadFont(AppServices.class.getResourceAsStream("/font/RobotoMono-Regular.ttf"), 13); - Font.loadFont(AppServices.class.getResourceAsStream("/font/RobotoMono-Italic.ttf"), 11); + Font.loadFont(AppServices.class.getResourceAsStream("/font/FragmentMono-Regular.ttf"), 13); + Font.loadFont(AppServices.class.getResourceAsStream("/font/FragmentMono-Italic.ttf"), 11); if(OsType.getCurrent() == OsType.MACOS) { Font.loadFont(AppServices.class.getResourceAsStream("/font/LiberationSans-Regular.ttf"), 13); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/ComboBoxTextField.java b/src/main/java/com/sparrowwallet/sparrow/control/ComboBoxTextField.java index bf893986..afce0cf8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/ComboBoxTextField.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/ComboBoxTextField.java @@ -6,10 +6,16 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.control.ComboBox; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.input.Clipboard; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import org.controlsfx.control.textfield.CustomTextField; +import java.util.List; + public class ComboBoxTextField extends CustomTextField { private final ObjectProperty> comboProperty = new SimpleObjectProperty<>(); @@ -68,4 +74,53 @@ public class ComboBoxTextField extends CustomTextField { public void setComboProperty(ComboBox comboProperty) { this.comboProperty.set(comboProperty); } + + public ContextMenu getCustomContextMenu(List customItems) { + return new CustomContextMenu(customItems); + } + + public class CustomContextMenu extends ContextMenu { + public CustomContextMenu(List customItems) { + super(); + setFont(null); + + MenuItem undo = new MenuItem("Undo"); + undo.setOnAction(_ -> undo()); + + MenuItem redo = new MenuItem("Redo"); + redo.setOnAction(_ -> redo()); + + MenuItem cut = new MenuItem("Cut"); + cut.setOnAction(_ -> cut()); + + MenuItem copy = new MenuItem("Copy"); + copy.setOnAction(_ -> copy()); + + MenuItem paste = new MenuItem("Paste"); + paste.setOnAction(_ -> paste()); + + MenuItem delete = new MenuItem("Delete"); + delete.setOnAction(_ -> deleteText(getSelection())); + + MenuItem selectAll = new MenuItem("Select All"); + selectAll.setOnAction(_ -> selectAll()); + + getItems().addAll(undo, redo, new SeparatorMenuItem(), cut, copy, paste, delete, new SeparatorMenuItem(), selectAll); + getItems().addAll(customItems); + + setOnShowing(_ -> { + boolean hasSelection = getSelection().getLength() > 0; + boolean hasText = getText() != null && !getText().isEmpty(); + boolean clipboardHasContent = Clipboard.getSystemClipboard().hasString(); + + undo.setDisable(!isUndoable()); + redo.setDisable(!isRedoable()); + cut.setDisable(!isEditable() || !hasSelection); + copy.setDisable(!hasSelection); + paste.setDisable(!isEditable() || !clipboardHasContent); + delete.setDisable(!hasSelection); + selectAll.setDisable(!hasText); + }); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java index 26b65f4c..7becb2d8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java @@ -4,6 +4,8 @@ import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.OsType; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.bip47.PaymentCode; +import com.sparrowwallet.drongo.dns.DnsPayment; +import com.sparrowwallet.drongo.dns.DnsPaymentCache; import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.TransactionOutput; import com.sparrowwallet.drongo.uri.BitcoinURI; @@ -693,9 +695,10 @@ public class TransactionDiagram extends GridPane { Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment); WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null; Wallet toBip47Wallet = getBip47SendWallet(payment); + DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment.getAddress()); Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + getSatsValue(payment.getAmount()) + " sats to " - + (payment instanceof AdditionalPayment ? (isExpanded() ? "\n" : "(click to expand)\n") + payment : (toWallet == null ? (payment.getLabel() == null ? (toNode != null ? toNode : (toBip47Wallet == null ? "external address" : toBip47Wallet.getDisplayName())) : payment.getLabel()) : toWallet.getFullDisplayName()) + "\n" + payment.getAddress().toString()) + + (payment instanceof AdditionalPayment ? (isExpanded() ? "\n" : "(click to expand)\n") + payment : (toWallet == null ? (dnsPayment == null ? (payment.getLabel() == null ? (toNode != null ? toNode : (toBip47Wallet == null ? "external address" : toBip47Wallet.getDisplayName())) : payment.getLabel()) : dnsPayment.toString()) : toWallet.getFullDisplayName()) + "\n" + payment.getAddress().toString()) + (walletTx.isDuplicateAddress(payment) ? " (Duplicate)" : "")); recipientTooltip.getStyleClass().add("recipient-label"); recipientTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY)); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java index 0d0282b3..c87acb56 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java @@ -1,5 +1,7 @@ package com.sparrowwallet.sparrow.control; +import com.sparrowwallet.drongo.dns.DnsPayment; +import com.sparrowwallet.drongo.dns.DnsPaymentCache; import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; @@ -206,7 +208,9 @@ public class TransactionDiagramLabel extends HBox { WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null; Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), payment); - String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment.getAddress().toString(); + DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment.getAddress()); + String recipient = dnsPayment == null ? payment.getAddress().toString() : dnsPayment.toString(); + String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + recipient; return getOutputLabel(glyph, text); } @@ -240,7 +244,7 @@ public class TransactionDiagramLabel extends HBox { icon.setGraphic(glyph); CopyableLabel label = new CopyableLabel(); - label.setFont(Font.font("Roboto Mono Italic", 13)); + label.setFont(Font.font("Fragment Mono Italic", 13)); label.setText(text); HBox output = new HBox(5); diff --git a/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java b/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java index 0732b55b..ada911f9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java +++ b/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java @@ -40,6 +40,10 @@ public class Payjoin { this.wallet = wallet; this.psbt = psbt; + if(payjoinURI.getAddress() == null) { + throw new IllegalArgumentException("Payjoin URI must have an address"); + } + for(PSBTInput psbtInput : psbt.getPsbtInputs()) { if(psbtInput.getUtxo() == null) { throw new IllegalArgumentException("Original PSBT for payjoin transaction must have non_witness_utxo or witness_utxo fields for all inputs"); diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 6a0a9c24..979649d6 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -180,6 +180,9 @@ public class HeadersController extends TransactionFormController implements Init @FXML private CopyableLabel blockTimestamp; + @FXML + private Form blockchainSpacerForm; + @FXML private Form signingWalletForm; @@ -451,6 +454,7 @@ public class HeadersController extends TransactionFormController implements Init headersForm.setWalletTransaction(getWalletTransaction(headersForm.getInputTransactions())); blockchainForm.managedProperty().bind(blockchainForm.visibleProperty()); + blockchainSpacerForm.managedProperty().bind(blockchainForm.managedProperty()); signingWalletForm.managedProperty().bind(signingWalletForm.visibleProperty()); sigHashForm.managedProperty().bind(sigHashForm.visibleProperty()); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java index 424556fd..71f6868b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java @@ -9,15 +9,13 @@ import com.sparrowwallet.drongo.address.P2PKHAddress; import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException; import com.sparrowwallet.drongo.bip47.PaymentCode; import com.sparrowwallet.drongo.crypto.ECKey; +import com.sparrowwallet.drongo.dns.DnsPaymentCache; import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.protocol.TransactionOutput; import com.sparrowwallet.drongo.uri.BitcoinURI; import com.sparrowwallet.drongo.wallet.*; -import com.sparrowwallet.sparrow.UnitFormat; -import com.sparrowwallet.sparrow.AppServices; -import com.sparrowwallet.sparrow.CurrencyRate; -import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.*; import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; @@ -25,6 +23,8 @@ import com.sparrowwallet.sparrow.io.CardApi; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.net.ExchangeSource; +import com.sparrowwallet.drongo.dns.DnsPayment; +import com.sparrowwallet.drongo.dns.DnsPaymentResolver; import com.sparrowwallet.sparrow.paynym.PayNym; import com.sparrowwallet.sparrow.paynym.PayNymDialog; import javafx.application.Platform; @@ -36,21 +36,32 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; +import javafx.concurrent.Service; +import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.layout.HBox; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.validation.ValidationResult; import org.controlsfx.validation.ValidationSupport; import org.controlsfx.validation.Validator; +import org.girod.javafx.svgimage.SVGImage; +import org.girod.javafx.svgimage.SVGLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.util.*; +import java.util.concurrent.TimeoutException; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -131,6 +142,8 @@ public class PaymentController extends WalletFormController implements Initializ private final ObjectProperty payNymProperty = new SimpleObjectProperty<>(); + private final ObjectProperty dnsPaymentProperty = new SimpleObjectProperty<>(); + private static final Wallet payNymWallet = new Wallet() { @Override public String getFullDisplayName() { @@ -145,6 +158,76 @@ public class PaymentController extends WalletFormController implements Initializ } }; + private final ChangeListener addressListener = new ChangeListener<>() { + @Override + public void changed(ObservableValue observable, String oldValue, String newValue) { + address.leftProperty().set(null); + + if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) { + payNymProperty.set(null); + } + + if(dnsPaymentProperty.get() != null && !newValue.equals(dnsPaymentProperty.get().hrn())) { + dnsPaymentProperty.set(null); + } + + try { + BitcoinURI bitcoinURI = new BitcoinURI(newValue); + Platform.runLater(() -> updateFromURI(bitcoinURI)); + return; + } catch(Exception e) { + //ignore, not a URI + } + + String dnsPaymentHrn = getDnsPaymentHrn(newValue); + if(dnsPaymentHrn != null) { + DnsPaymentService dnsPaymentService = new DnsPaymentService(dnsPaymentHrn); + dnsPaymentService.setOnSucceeded(_ -> dnsPaymentService.getValue().ifPresent(dnsPayment -> setDnsPayment(dnsPayment))); + dnsPaymentService.setOnFailed(failEvent -> { + if(failEvent.getSource().getException() != null && !(failEvent.getSource().getException().getCause() instanceof TimeoutException)) { + AppServices.showErrorDialog("Validation failed for " + dnsPaymentHrn, failEvent.getSource().getException().getMessage()); + } + }); + dnsPaymentService.start(); + return; + } + + if(sendController.getWalletForm().getWallet().hasPaymentCode()) { + try { + PaymentCode paymentCode = new PaymentCode(newValue); + Wallet recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, sendController.getWalletForm().getWallet().getScriptType()); + if(recipientBip47Wallet == null && sendController.getWalletForm().getWallet().getScriptType() != ScriptType.P2PKH) { + recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, ScriptType.P2PKH); + } + + if(recipientBip47Wallet != null) { + PayNym payNym = PayNym.fromWallet(recipientBip47Wallet); + Platform.runLater(() -> setPayNym(payNym)); + } else if(!paymentCode.equals(sendController.getWalletForm().getWallet().getPaymentCode())) { + ButtonType previewType = new ButtonType("Preview Transaction", ButtonBar.ButtonData.YES); + Optional optButton = AppServices.showAlertDialog("Send notification transaction?", "This payment code is not yet linked with a notification transaction. Send a notification transaction?", Alert.AlertType.CONFIRMATION, ButtonType.CANCEL, previewType); + if(optButton.isPresent() && optButton.get() == previewType) { + Payment payment = new Payment(paymentCode.getNotificationAddress(), "Link " + paymentCode.toAbbreviatedString(), MINIMUM_P2PKH_OUTPUT_SATS, false); + Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(sendController.getWalletForm().getWallet(), List.of(payment), List.of(new byte[80]), paymentCode))); + } else { + Platform.runLater(() -> address.setText("")); + } + } + } catch(Exception e) { + //ignore, not a payment code + } + } + + revalidateAmount(); + maxButton.setDisable(!isMaxButtonEnabled()); + sendController.updateTransaction(); + + if(validationSupport != null) { + validationSupport.setErrorDecorationEnabled(true); + } + } + }; + @Override public void initialize(URL location, ResourceBundle resources) { EventManager.get().register(this); @@ -210,6 +293,24 @@ public class PaymentController extends WalletFormController implements Initializ revalidateAmount(); }); + dnsPaymentProperty.addListener((observable, oldValue, dnsPayment) -> { + if(dnsPayment != null) { + MenuItem copyMenuItem = new MenuItem("Copy URI"); + copyMenuItem.setOnAction(e -> { + ClipboardContent content = new ClipboardContent(); + content.putString(dnsPayment.bitcoinURI().toURIString()); + Clipboard.getSystemClipboard().setContent(content); + }); + address.setContextMenu(address.getCustomContextMenu(List.of(copyMenuItem))); + } else { + address.setContextMenu(address.getCustomContextMenu(Collections.emptyList())); + } + + revalidateAmount(); + maxButton.setDisable(!isMaxButtonEnabled()); + sendController.updateTransaction(); + }); + address.setTextFormatter(new TextFormatter<>(change -> { String controlNewText = change.getControlNewText(); if(!controlNewText.equals(controlNewText.trim())) { @@ -222,55 +323,8 @@ public class PaymentController extends WalletFormController implements Initializ return change; })); - address.textProperty().addListener((observable, oldValue, newValue) -> { - address.leftProperty().set(null); - - if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) { - payNymProperty.set(null); - } - - try { - BitcoinURI bitcoinURI = new BitcoinURI(newValue); - Platform.runLater(() -> updateFromURI(bitcoinURI)); - return; - } catch(Exception e) { - //ignore, not a URI - } - - if(sendController.getWalletForm().getWallet().hasPaymentCode()) { - try { - PaymentCode paymentCode = new PaymentCode(newValue); - Wallet recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, sendController.getWalletForm().getWallet().getScriptType()); - if(recipientBip47Wallet == null && sendController.getWalletForm().getWallet().getScriptType() != ScriptType.P2PKH) { - recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, ScriptType.P2PKH); - } - - if(recipientBip47Wallet != null) { - PayNym payNym = PayNym.fromWallet(recipientBip47Wallet); - Platform.runLater(() -> setPayNym(payNym)); - } else if(!paymentCode.equals(sendController.getWalletForm().getWallet().getPaymentCode())) { - ButtonType previewType = new ButtonType("Preview Transaction", ButtonBar.ButtonData.YES); - Optional optButton = AppServices.showAlertDialog("Send notification transaction?", "This payment code is not yet linked with a notification transaction. Send a notification transaction?", Alert.AlertType.CONFIRMATION, ButtonType.CANCEL, previewType); - if(optButton.isPresent() && optButton.get() == previewType) { - Payment payment = new Payment(paymentCode.getNotificationAddress(), "Link " + paymentCode.toAbbreviatedString(), MINIMUM_P2PKH_OUTPUT_SATS, false); - Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(sendController.getWalletForm().getWallet(), List.of(payment), List.of(new byte[80]), paymentCode))); - } else { - Platform.runLater(() -> address.setText("")); - } - } - } catch(Exception e) { - //ignore, not a payment code - } - } - - revalidateAmount(); - maxButton.setDisable(!isMaxButtonEnabled()); - sendController.updateTransaction(); - - if(validationSupport != null) { - validationSupport.setErrorDecorationEnabled(true); - } - }); + address.textProperty().addListener(addressListener); + address.setContextMenu(address.getCustomContextMenu(Collections.emptyList())); label.textProperty().addListener((observable, oldValue, newValue) -> { maxButton.setDisable(!isMaxButtonEnabled()); @@ -328,6 +382,23 @@ public class PaymentController extends WalletFormController implements Initializ } } + public void setDnsPayment(DnsPayment dnsPayment) { + if(dnsPayment.bitcoinURI().getAddress() == null) { + AppServices.showWarningDialog("No Address Provided", "The DNS payment instruction for " + dnsPayment.hrn() + " resolved correctly but did not contain a Bitcoin address."); + return; + } + + DnsPaymentCache.putDnsPayment(dnsPayment.bitcoinURI().getAddress(), dnsPayment); + dnsPaymentProperty.set(dnsPayment); + address.setText(dnsPayment.hrn()); + revalidate(address, addressListener); + address.leftProperty().set(getBitcoinCharacter()); + if(label.getText().isEmpty()) { + label.setText("To " + dnsPayment); + } + label.requestFocus(); + } + private void updateOpenWallets() { updateOpenWallets(AppServices.get().getOpenWallets().keySet()); } @@ -399,6 +470,11 @@ public class PaymentController extends WalletFormController implements Initializ } private Address getRecipientAddress() throws InvalidAddressException { + DnsPayment dnsPayment = dnsPaymentProperty.get(); + if(dnsPayment != null) { + return dnsPayment.bitcoinURI().getAddress(); + } + PayNym payNym = payNymProperty.get(); if(payNym == null) { return Address.fromString(address.getText()); @@ -422,6 +498,25 @@ public class PaymentController extends WalletFormController implements Initializ throw new InvalidAddressException(); } + private String getDnsPaymentHrn(String value) { + String hrn = value; + if(value.endsWith(".")) { + return null; + } + + if(hrn.startsWith("₿")) { + hrn = hrn.substring(1); + } + + String[] addressParts = hrn.split("@"); + if(addressParts.length == 2 && addressParts[1].indexOf('.') > -1 && addressParts[1].substring(addressParts[1].indexOf('.') + 1).length() > 1 && + StandardCharsets.US_ASCII.newEncoder().canEncode(hrn)) { + return hrn; + } + + return null; + } + private Wallet getWalletForPayNym(PayNym payNym) throws InvalidPaymentCodeException { Wallet masterWallet = sendController.getWalletForm().getMasterWallet(); return masterWallet.getChildWallet(new PaymentCode(payNym.paymentCode().toString()), payNym.segwit() ? ScriptType.P2WPKH : ScriptType.P2PKH); @@ -565,6 +660,7 @@ public class PaymentController extends WalletFormController implements Initializ dustAmountProperty.set(false); payNymProperty.set(null); + dnsPaymentProperty.set(null); } public void setMaxInput(ActionEvent event) { @@ -625,7 +721,7 @@ public class PaymentController extends WalletFormController implements Initializ setRecipientValueSats(bitcoinURI.getAmount()); setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), bitcoinURI.getAmount()); } - if(bitcoinURI.getPayjoinUrl() != null) { + if(bitcoinURI.getAddress() != null && bitcoinURI.getPayjoinUrl() != null) { AppServices.addPayjoinURI(bitcoinURI); } sendController.updateTransaction(); @@ -676,10 +772,33 @@ public class PaymentController extends WalletFormController implements Initializ public static Glyph getPayNymGlyph() { Glyph payNymGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ROBOT); payNymGlyph.getStyleClass().add("paynym-icon"); - payNymGlyph.setFontSize(12); + payNymGlyph.setFontSize(10); return payNymGlyph; } + public static Node getBitcoinCharacter() { + try { + URL url; + if(Config.get().getTheme() == Theme.DARK) { + url = AppServices.class.getResource("/image/bitcoin-character-invert.svg"); + } else { + url = AppServices.class.getResource("/image/bitcoin-character.svg"); + } + if(url != null) { + SVGImage svgImage = SVGLoader.load(url); + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER); + hBox.getChildren().add(svgImage); + hBox.setPadding(new Insets(0, 2, 0, 4)); + return hBox; + } + } catch(Exception e) { + log.error("Could not load bitcoin character"); + } + + return null; + } + public static Glyph getNfcCardGlyph() { Glyph nfcCardGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WIFI); nfcCardGlyph.getStyleClass().add("nfccard-icon"); @@ -723,4 +842,23 @@ public class PaymentController extends WalletFormController implements Initializ public void openWallets(OpenWalletsEvent event) { updateOpenWallets(event.getWallets()); } + + private static class DnsPaymentService extends Service> { + private final String hrn; + + public DnsPaymentService(String hrn) { + this.hrn = hrn; + } + + @Override + protected Task> createTask() { + return new Task<>() { + @Override + protected Optional call() throws Exception { + DnsPaymentResolver resolver = new DnsPaymentResolver(hrn); + return resolver.resolve(); + } + }; + } + } } diff --git a/src/main/resources/com/sparrowwallet/sparrow/general.css b/src/main/resources/com/sparrowwallet/sparrow/general.css index 26d8050f..bb76d42c 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/general.css +++ b/src/main/resources/com/sparrowwallet/sparrow/general.css @@ -46,7 +46,7 @@ .id, .fixed-width { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } .form-separator { diff --git a/src/main/resources/com/sparrowwallet/sparrow/script.css b/src/main/resources/com/sparrowwallet/sparrow/script.css index bb05c9c5..6302a549 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/script.css +++ b/src/main/resources/com/sparrowwallet/sparrow/script.css @@ -29,7 +29,7 @@ .virtualized-scroll-pane .code-area, .uneditable-codearea { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; -fx-padding: 4; -fx-fill: -fx-text-inner-color; } diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css index 247c74e1..74dd443d 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css @@ -44,7 +44,7 @@ #transactionDiagram .input-label, #transactionDiagram .recipient-label, #transactionDiagram .change-label, #transactionDiagram .fee-tooltip, #transactionDiagram .transaction-tooltip { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } #transactionDiagram .fee-warning-icon { diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml index b1cfdcbd..216dfaa6 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml @@ -40,7 +40,9 @@ - + + +
@@ -74,9 +76,11 @@ - + + + @@ -176,19 +180,41 @@ - -
- - - - - - - - -
-
+ + + + + + +
+ + + + + + + + +
+
+ +
+ + + + + + + + +
+ +
@@ -236,7 +262,7 @@
- + diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/inputs.css b/src/main/resources/com/sparrowwallet/sparrow/transaction/inputs.css index 2a4fb18e..548e4fe2 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/inputs.css +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/inputs.css @@ -23,7 +23,7 @@ .chart-legend-item { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } .default-color0.chart-pie { -fx-pie-color: #ca1243 } diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/outputs.css b/src/main/resources/com/sparrowwallet/sparrow/transaction/outputs.css index 67a33363..202b781e 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/outputs.css +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/outputs.css @@ -23,7 +23,7 @@ .chart-legend-item { -fx-font-size: 13; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } .default-color7.chart-pie { -fx-pie-color: #0184bc } diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/transaction.css b/src/main/resources/com/sparrowwallet/sparrow/transaction/transaction.css index b0c20428..974c3f13 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/transaction.css +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/transaction.css @@ -1,7 +1,7 @@ #txhex { -fx-background-color: -fx-control-inner-background; -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; -fx-padding: 2; color-0: #ca1243; color-1: #d75f00; diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.css b/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.css index ddb8a2e1..501db4aa 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.css +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.css @@ -15,7 +15,7 @@ #fingerprint, #derivation, #xpub { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } #type { diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml index e0d6ac5f..a0c12178 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml @@ -21,7 +21,7 @@ - + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml index ea36b98a..d311c0d6 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml @@ -26,7 +26,7 @@ - + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css index d05f4ae2..29ea992c 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css @@ -71,7 +71,7 @@ #transactionDiagram .input-label, #transactionDiagram .recipient-label, #transactionDiagram .change-label, #transactionDiagram .fee-tooltip, #transactionDiagram .transaction-tooltip { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } #transactionDiagram .fee-warning-icon { diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml index df592be1..254cef77 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml @@ -36,7 +36,7 @@ - + @@ -152,9 +152,9 @@ - - - + + + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.css b/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.css index d77948d6..45687f99 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.css +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.css @@ -31,7 +31,7 @@ .address-cell, .utxo-row.entry-cell { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } .cell > .hyperlink { @@ -149,7 +149,7 @@ .address-text-field { -fx-font-size: 13px; - -fx-font-family: 'Roboto Mono'; + -fx-font-family: 'Fragment Mono Regular'; } .unconfirmed-row { diff --git a/src/main/resources/font/FragmentMono-Italic.ttf b/src/main/resources/font/FragmentMono-Italic.ttf new file mode 100644 index 00000000..61450624 Binary files /dev/null and b/src/main/resources/font/FragmentMono-Italic.ttf differ diff --git a/src/main/resources/font/FragmentMono-Regular.ttf b/src/main/resources/font/FragmentMono-Regular.ttf new file mode 100644 index 00000000..8110928b Binary files /dev/null and b/src/main/resources/font/FragmentMono-Regular.ttf differ diff --git a/src/main/resources/font/RobotoMono-Italic.ttf b/src/main/resources/font/RobotoMono-Italic.ttf deleted file mode 100644 index 61e53033..00000000 Binary files a/src/main/resources/font/RobotoMono-Italic.ttf and /dev/null differ diff --git a/src/main/resources/font/RobotoMono-Regular.ttf b/src/main/resources/font/RobotoMono-Regular.ttf deleted file mode 100644 index 7c4ce36a..00000000 Binary files a/src/main/resources/font/RobotoMono-Regular.ttf and /dev/null differ diff --git a/src/main/resources/image/bitcoin-character-invert.svg b/src/main/resources/image/bitcoin-character-invert.svg new file mode 100644 index 00000000..14fae0d8 --- /dev/null +++ b/src/main/resources/image/bitcoin-character-invert.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/image/bitcoin-character.svg b/src/main/resources/image/bitcoin-character.svg new file mode 100644 index 00000000..e64bbb61 --- /dev/null +++ b/src/main/resources/image/bitcoin-character.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 5a93e64f..df2f8575 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -36,6 +36,7 @@ +