diff --git a/contrib/android/bitcoin_intent.xml b/contrib/android/bitcoin_intent.xml
index 87553b2c826f..3592e45eb764 100644
--- a/contrib/android/bitcoin_intent.xml
+++ b/contrib/android/bitcoin_intent.xml
@@ -5,4 +5,6 @@
+
+
diff --git a/contrib/build-wine/electrum.nsi b/contrib/build-wine/electrum.nsi
index 6aa4ce22be72..23111d5e4306 100644
--- a/contrib/build-wine/electrum.nsi
+++ b/contrib/build-wine/electrum.nsi
@@ -189,7 +189,7 @@ Section
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME} Testnet.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" "--testnet" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" 0
- ;Links bitcoin: and lightning: URIs to Electrum
+ ;Links bitcoin:, lightning: and lnurl LUD-17 URIs to Electrum
WriteRegStr HKCU "Software\Classes\bitcoin" "" "URL:bitcoin Protocol"
WriteRegStr HKCU "Software\Classes\bitcoin" "URL Protocol" ""
WriteRegStr HKCU "Software\Classes\bitcoin" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
@@ -198,6 +198,14 @@ Section
WriteRegStr HKCU "Software\Classes\lightning" "URL Protocol" ""
WriteRegStr HKCU "Software\Classes\lightning" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
WriteRegStr HKCU "Software\Classes\lightning\shell\open\command" "" "$\"$INSTDIR\electrum-${PRODUCT_VERSION}.exe$\" $\"%1$\""
+ WriteRegStr HKCU "Software\Classes\lnurlp" "" "URL:lnurlp Protocol"
+ WriteRegStr HKCU "Software\Classes\lnurlp" "URL Protocol" ""
+ WriteRegStr HKCU "Software\Classes\lnurlp" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
+ WriteRegStr HKCU "Software\Classes\lnurlp\shell\open\command" "" "$\"$INSTDIR\electrum-${PRODUCT_VERSION}.exe$\" $\"%1$\""
+ WriteRegStr HKCU "Software\Classes\lnurlw" "" "URL:lnurlw Protocol"
+ WriteRegStr HKCU "Software\Classes\lnurlw" "URL Protocol" ""
+ WriteRegStr HKCU "Software\Classes\lnurlw" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
+ WriteRegStr HKCU "Software\Classes\lnurlw\shell\open\command" "" "$\"$INSTDIR\electrum-${PRODUCT_VERSION}.exe$\" $\"%1$\""
;Adds an uninstaller possibility to Windows Uninstall or change a program section
WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
@@ -229,6 +237,9 @@ Section "Uninstall"
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
DeleteRegKey HKCU "Software\Classes\bitcoin"
+ DeleteRegKey HKCU "Software\Classes\lightning"
+ DeleteRegKey HKCU "Software\Classes\lnurlp"
+ DeleteRegKey HKCU "Software\Classes\lnurlw"
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
DeleteRegKey HKCU "${PRODUCT_UNINST_KEY}"
SectionEnd
diff --git a/contrib/osx/pyinstaller.spec b/contrib/osx/pyinstaller.spec
index 5cd612eda437..6adb5513c9fd 100644
--- a/contrib/osx/pyinstaller.spec
+++ b/contrib/osx/pyinstaller.spec
@@ -135,7 +135,7 @@ app = BUNDLE(
'CFBundleURLTypes':
[{
'CFBundleURLName': 'bitcoin',
- 'CFBundleURLSchemes': ['bitcoin', 'lightning', ],
+ 'CFBundleURLSchemes': ['bitcoin', 'lightning', 'lnurlp', 'lnurlw', ],
}],
'LSMinimumSystemVersion': '11',
'NSCameraUsageDescription': 'Electrum would like to access the camera to scan for QR codes',
diff --git a/electrum.desktop b/electrum.desktop
index 7434829a560f..8d8d9edbd3ce 100644
--- a/electrum.desktop
+++ b/electrum.desktop
@@ -15,7 +15,7 @@ StartupNotify=true
StartupWMClass=electrum
Terminal=false
Type=Application
-MimeType=x-scheme-handler/bitcoin;x-scheme-handler/lightning;
+MimeType=x-scheme-handler/bitcoin;x-scheme-handler/lightning;x-scheme-handler/lnurlp;x-scheme-handler/lnurlw;
Actions=Testnet;
Keywords=crypto;currency;BTC
diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py
index e3971561a087..8bf7cc70db3c 100644
--- a/electrum/gui/qml/qeapp.py
+++ b/electrum/gui/qml/qeapp.py
@@ -23,6 +23,7 @@
from electrum.plugin import run_hook
from electrum.gui.common_qt.util import get_font_id
from electrum.util import profiler
+from electrum.lnurl import SUPPORTED_LNURL_SCHEMES
from .qeconfig import QEConfig
from .qedaemon import QEDaemon
@@ -235,7 +236,9 @@ def on_new_intent(self, intent):
data = str(intent.getDataString())
self.logger.debug(f'received intent: {repr(data)}')
scheme = str(intent.getScheme()).lower()
- if scheme == BITCOIN_BIP21_URI_SCHEME or scheme == LIGHTNING_URI_SCHEME:
+ if scheme == BITCOIN_BIP21_URI_SCHEME \
+ or scheme == LIGHTNING_URI_SCHEME \
+ or scheme in SUPPORTED_LNURL_SCHEMES:
self.uriReceived.emit(data)
def startup_finished(self):
diff --git a/electrum/lnurl.py b/electrum/lnurl.py
index a95bf8bcc633..e08f254d6047 100644
--- a/electrum/lnurl.py
+++ b/electrum/lnurl.py
@@ -21,6 +21,9 @@
_logger = get_logger(__name__)
+SUPPORTED_LNURL_SCHEMES = ('lnurlp', 'lnurlw')
+
+
class LNURLError(Exception): pass
class UntrustedLNURLError(LNURLError):
diff --git a/electrum/payment_identifier.py b/electrum/payment_identifier.py
index 5883aadfb669..198bb68a19d7 100644
--- a/electrum/payment_identifier.py
+++ b/electrum/payment_identifier.py
@@ -16,7 +16,7 @@
from .transaction import PartialTxOutput
from .lnurl import (decode_lnurl, request_lnurl, callback_lnurl, LNURLError,
lightning_address_to_url, try_resolve_lnurlpay, LNURL6Data,
- LNURL3Data, LNURLData)
+ LNURL3Data, LNURLData, SUPPORTED_LNURL_SCHEMES)
from .bitcoin import opcodes, construct_script
from .lnaddr import LnInvoiceException
from .lnutil import IncompatibleOrInsaneFeatures
@@ -45,9 +45,25 @@ def remove_uri_prefix(data: str, *, prefix: str) -> str:
return data
+def maybe_extract_url_from_lud_17_uri(data: str) -> Optional[str]:
+ """https://github.com/lnurl/luds/blob/luds/17.md"""
+ data = data.strip()
+ try:
+ parsed = urllib.parse.urlsplit(data)
+ except ValueError:
+ return None
+ if parsed.scheme not in SUPPORTED_LNURL_SCHEMES:
+ return None
+ if not (host := parsed.hostname) or not parsed.path:
+ return None
+ is_onion = host.endswith('.onion')
+ url_scheme = 'http' if is_onion else 'https'
+ return urllib.parse.urlunsplit(parsed._replace(scheme=url_scheme))
+
+
RE_ALIAS = r'(.*?)\s*\<([0-9A-Za-z]{1,})\>'
-RE_EMAIL = r'\b[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+\.)+[A-Z|a-z]{2,7}\b'
-RE_DOMAIN = r'\b([A-Za-z0-9-]+\.)+[A-Z|a-z]{2,7}\b'
+RE_EMAIL = r'\b[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+\.)+[A-Za-z]{2,7}\b'
+RE_DOMAIN = r'\b([A-Za-z0-9-]+\.)+[A-Za-z]{2,7}\b'
RE_SCRIPT_FN = r'script\((.*)\)'
@@ -98,6 +114,7 @@ class PaymentIdentifier(Logger):
* openalias
* bip21 URI
* lightning-URI (containing bolt11 or lnurl)
+ * lnurl-URI (lud17 lnurlw/lnurlp URI)
* bolt11 invoice
* lnurl
* lightning address
@@ -228,6 +245,10 @@ def parse(self, text: str):
self.logger.debug(f'Exception cause {e.args!r}')
return
self.set_state(PaymentIdentifierState.AVAILABLE)
+ elif lnurl_url := maybe_extract_url_from_lud_17_uri(text):
+ self._type = PaymentIdentifierType.LNURL
+ self.lnurl = lnurl_url
+ self.set_state(PaymentIdentifierState.NEED_RESOLVE)
elif text.lower().startswith(BITCOIN_BIP21_URI_SCHEME + ':'):
try:
out = parse_bip21_URI(text)
diff --git a/tests/test_payment_identifier.py b/tests/test_payment_identifier.py
index 046f5ba11b73..f19fb6baaeaf 100644
--- a/tests/test_payment_identifier.py
+++ b/tests/test_payment_identifier.py
@@ -184,6 +184,17 @@ def test_lnurl_basic(self):
self.assertEqual(PaymentIdentifierType.LNURL, pi.type)
self.assertTrue(pi.need_resolve())
+ # test with lud17 prefix
+ unsupported_lud_17_lnurl_c = f"lnurlc://service.io/?q=3fc3645b439ce8e7"
+ pi = PaymentIdentifier(None, unsupported_lud_17_lnurl_c)
+ self.assertFalse(pi.is_valid())
+
+ valid_lud_17_lnurl_w = f"lnurlw://service.io/?q=3fc3645b439ce8e7"
+ pi = PaymentIdentifier(None, valid_lud_17_lnurl_w)
+ self.assertTrue(pi.is_valid())
+ self.assertEqual(PaymentIdentifierType.LNURL, pi.type)
+ self.assertTrue(pi.need_resolve())
+
@patch('electrum.payment_identifier.request_lnurl')
def test_lnurl_pay_resolve(self, mock_request_lnurl):
"""Test LNURL-pay (LNURL6) with mocked resolve"""