diff --git a/build.gradle b/build.gradle
index bf97cc59..181221c2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -164,7 +164,7 @@ jlink {
appVersion = "${sparrowVersion}"
skipInstaller = os.macOsX
imageOptions = []
- installerOptions = ['--file-associations', 'src/main/deploy/associations.properties', '--license-file', 'LICENSE']
+ installerOptions = ['--file-associations', 'src/main/deploy/psbt.properties', '--file-associations', 'src/main/deploy/bitcoin.properties', '--file-associations', 'src/main/deploy/aopp.properties', '--license-file', 'LICENSE']
if(os.windows) {
installerOptions += ['--win-per-user-install', '--win-dir-chooser', '--win-menu', '--win-shortcut']
imageOptions += ['--icon', 'src/main/deploy/package/windows/sparrow.ico']
diff --git a/drongo b/drongo
index db9617ee..cc32285d 160000
--- a/drongo
+++ b/drongo
@@ -1 +1 @@
-Subproject commit db9617ee10383bb78e71ec2252d92bb7fe639440
+Subproject commit cc32285d58fcd1120b27797ff1d882ae4ae8a1ed
diff --git a/src/main/deploy/aopp.properties b/src/main/deploy/aopp.properties
new file mode 100644
index 00000000..0f7aefb1
--- /dev/null
+++ b/src/main/deploy/aopp.properties
@@ -0,0 +1,2 @@
+mime-type=x-scheme-handler/aopp
+description=Verify Address Ownership URI
\ No newline at end of file
diff --git a/src/main/deploy/bitcoin.properties b/src/main/deploy/bitcoin.properties
new file mode 100644
index 00000000..c5e54ebb
--- /dev/null
+++ b/src/main/deploy/bitcoin.properties
@@ -0,0 +1,2 @@
+mime-type=x-scheme-handler/bitcoin
+description=Bitcoin Scheme URI
\ No newline at end of file
diff --git a/src/main/deploy/package/osx/Info.plist b/src/main/deploy/package/osx/Info.plist
index 590ebe2c..1ffca736 100644
--- a/src/main/deploy/package/osx/Info.plist
+++ b/src/main/deploy/package/osx/Info.plist
@@ -35,5 +35,24 @@
true
NSCameraUsageDescription
Sparrow requires access to the camera in order to scan QR codes
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ com.sparrowwallet.sparrow.bitcoin
+ CFBundleURLSchemes
+
+ bitcoin
+
+
+
+ CFBundleURLName
+ com.sparrowwallet.sparrow.aopp
+ CFBundleURLSchemes
+
+ aopp
+
+
+
\ No newline at end of file
diff --git a/src/main/deploy/associations.properties b/src/main/deploy/psbt.properties
similarity index 51%
rename from src/main/deploy/associations.properties
rename to src/main/deploy/psbt.properties
index 717f3943..c985fcde 100644
--- a/src/main/deploy/associations.properties
+++ b/src/main/deploy/psbt.properties
@@ -1,3 +1,3 @@
-extension=psbt
mime-type=application/octet-stream
+extension=psbt
description=Partially Signed Bitcoin Transaction
\ No newline at end of file
diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java
index 87c3a40b..e5aa6029 100644
--- a/src/main/java/com/sparrowwallet/sparrow/AppController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java
@@ -1795,4 +1795,9 @@ public class AppController implements Initializable {
openTransactionFromQR(null);
}
}
+
+ @Subscribe
+ public void sendAction(SendActionEvent event) {
+ selectTab(event.getWallet());
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java
index 3aefbd2d..cb441bce 100644
--- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java
+++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java
@@ -29,7 +29,10 @@ import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
import javafx.scene.text.Font;
import javafx.stage.Screen;
import javafx.stage.Stage;
@@ -40,14 +43,17 @@ import org.controlsfx.control.HyperlinkLabel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import java.net.URI;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -135,6 +141,8 @@ public class AppServices {
restartServices();
}
}
+
+ addURIHandlers();
}
private void restartServices() {
@@ -591,6 +599,50 @@ public class AppServices {
}
}
+ public static void addURIHandlers() {
+ Desktop.getDesktop().setOpenURIHandler(event -> {
+ URI uri = event.getURI();
+ if("bitcoin".equals(uri.getScheme())) {
+ Platform.runLater(() -> openBitcoinUri(uri));
+ } else if("aopp".equals(uri.getScheme())) {
+ log.info(uri.toString());
+ }
+ });
+ }
+
+ private static void openBitcoinUri(URI uri) {
+ try {
+ BitcoinURI bitcoinURI = new BitcoinURI(uri.toString());
+
+ Wallet wallet = null;
+ Set wallets = get().getOpenWallets().keySet();
+ if(wallets.isEmpty()) {
+ showErrorDialog("No wallet available", "Open a wallet to send to the provided bitcoin URI.");
+ } else if(wallets.size() == 1) {
+ wallet = wallets.iterator().next();
+ } else {
+ ChoiceDialog walletChoiceDialog = new ChoiceDialog<>(wallets.iterator().next(), wallets);
+ walletChoiceDialog.setTitle("Choose Wallet");
+ walletChoiceDialog.setHeaderText("Choose a wallet to pay from");
+ Image image = new Image("/image/sparrow-small.png");
+ walletChoiceDialog.getDialogPane().setGraphic(new ImageView(image));
+ AppServices.setStageIcon(walletChoiceDialog.getDialogPane().getScene().getWindow());
+ Optional optWallet = walletChoiceDialog.showAndWait();
+ if(optWallet.isPresent()) {
+ wallet = optWallet.get();
+ }
+ }
+
+ if(wallet != null) {
+ final Wallet sendingWallet = wallet;
+ EventManager.get().post(new SendActionEvent(sendingWallet, new ArrayList<>(sendingWallet.getWalletUtxos().keySet())));
+ Platform.runLater(() -> EventManager.get().post(new SendPaymentsEvent(sendingWallet, List.of(bitcoinURI.toPayment()))));
+ }
+ } catch(Exception e) {
+ showErrorDialog("Not a valid bitcoin URI", e.getMessage());
+ }
+ }
+
public static Font getMonospaceFont() {
return Font.font("Roboto Mono", 13);
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/event/SendPaymentsEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/SendPaymentsEvent.java
new file mode 100644
index 00000000..7dda67d7
--- /dev/null
+++ b/src/main/java/com/sparrowwallet/sparrow/event/SendPaymentsEvent.java
@@ -0,0 +1,24 @@
+package com.sparrowwallet.sparrow.event;
+
+import com.sparrowwallet.drongo.wallet.Payment;
+import com.sparrowwallet.drongo.wallet.Wallet;
+
+import java.util.List;
+
+public class SendPaymentsEvent {
+ private final Wallet wallet;
+ private final List payments;
+
+ public SendPaymentsEvent(Wallet wallet, List payments) {
+ this.wallet = wallet;
+ this.payments = payments;
+ }
+
+ public Wallet getWallet() {
+ return wallet;
+ }
+
+ public List getPayments() {
+ return payments;
+ }
+}
diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
index 477ef264..45e00797 100644
--- a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
@@ -282,7 +282,9 @@ public class PaymentController extends WalletFormController implements Initializ
if(payment.getLabel() != null) {
label.setText(payment.getLabel());
}
- setRecipientValueSats(payment.getAmount());
+ if(payment.getAmount() >= 0) {
+ setRecipientValueSats(payment.getAmount());
+ }
setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), payment.getAmount());
}
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java
index f62919be..2f4439bd 100644
--- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java
@@ -1074,6 +1074,17 @@ public class SendController extends WalletFormController implements Initializabl
}
}
+ @Subscribe
+ public void sendPayments(SendPaymentsEvent event) {
+ if(event.getWallet().equals(getWalletForm().getWallet())) {
+ if(event.getPayments() != null) {
+ clear(null);
+ setPayments(event.getPayments());
+ updateTransaction(event.getPayments() == null || event.getPayments().stream().anyMatch(Payment::isSendMax));
+ }
+ }
+ }
+
@Subscribe
public void bitcoinUnitChanged(BitcoinUnitChangedEvent event) {
BitcoinUnit unit = getBitcoinUnit(event.getBitcoinUnit());