pi: handle lud-17 URI payment identifier
LNURL-W/P can also be encoded in lud-17 form instead of bech32. https://github.com/lnurl/luds/blob/luds/17.md e.g. lnurlw://example.com/api/test123 lnurlp://example.com/api/test123
This commit is contained in:
@@ -5,4 +5,6 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="bitcoin" />
|
<data android:scheme="bitcoin" />
|
||||||
<data android:scheme="lightning" />
|
<data android:scheme="lightning" />
|
||||||
|
<data android:scheme="lnurlw" />
|
||||||
|
<data android:scheme="lnurlp" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -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
|
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:bitcoin Protocol"
|
||||||
WriteRegStr HKCU "Software\Classes\bitcoin" "URL Protocol" ""
|
WriteRegStr HKCU "Software\Classes\bitcoin" "URL Protocol" ""
|
||||||
WriteRegStr HKCU "Software\Classes\bitcoin" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
|
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" "URL Protocol" ""
|
||||||
WriteRegStr HKCU "Software\Classes\lightning" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
|
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\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
|
;Adds an uninstaller possibility to Windows Uninstall or change a program section
|
||||||
WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
|
WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ app = BUNDLE(
|
|||||||
'CFBundleURLTypes':
|
'CFBundleURLTypes':
|
||||||
[{
|
[{
|
||||||
'CFBundleURLName': 'bitcoin',
|
'CFBundleURLName': 'bitcoin',
|
||||||
'CFBundleURLSchemes': ['bitcoin', 'lightning', ],
|
'CFBundleURLSchemes': ['bitcoin', 'lightning', 'lnurlp', 'lnurlw', ],
|
||||||
}],
|
}],
|
||||||
'LSMinimumSystemVersion': '11',
|
'LSMinimumSystemVersion': '11',
|
||||||
'NSCameraUsageDescription': 'Electrum would like to access the camera to scan for QR codes',
|
'NSCameraUsageDescription': 'Electrum would like to access the camera to scan for QR codes',
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ StartupNotify=true
|
|||||||
StartupWMClass=electrum
|
StartupWMClass=electrum
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
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;
|
Actions=Testnet;
|
||||||
Keywords=crypto;currency;BTC
|
Keywords=crypto;currency;BTC
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from electrum.network import Network
|
|||||||
from electrum.plugin import run_hook
|
from electrum.plugin import run_hook
|
||||||
from electrum.gui.common_qt.util import get_font_id
|
from electrum.gui.common_qt.util import get_font_id
|
||||||
from electrum.util import profiler
|
from electrum.util import profiler
|
||||||
|
from electrum.lnurl import SUPPORTED_LNURL_SCHEMES
|
||||||
|
|
||||||
from .qeconfig import QEConfig
|
from .qeconfig import QEConfig
|
||||||
from .qedaemon import QEDaemon
|
from .qedaemon import QEDaemon
|
||||||
@@ -235,7 +236,9 @@ class QEAppController(BaseCrashReporter, QObject):
|
|||||||
data = str(intent.getDataString())
|
data = str(intent.getDataString())
|
||||||
self.logger.debug(f'received intent: {repr(data)}')
|
self.logger.debug(f'received intent: {repr(data)}')
|
||||||
scheme = str(intent.getScheme()).lower()
|
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)
|
self.uriReceived.emit(data)
|
||||||
|
|
||||||
def startup_finished(self):
|
def startup_finished(self):
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ from electrum.i18n import _
|
|||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
SUPPORTED_LNURL_SCHEMES = ('lnurlp', 'lnurlw')
|
||||||
|
|
||||||
|
|
||||||
class LNURLError(Exception): pass
|
class LNURLError(Exception): pass
|
||||||
|
|
||||||
class UntrustedLNURLError(LNURLError):
|
class UntrustedLNURLError(LNURLError):
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from .util import get_asyncio_loop, log_exceptions
|
|||||||
from .transaction import PartialTxOutput
|
from .transaction import PartialTxOutput
|
||||||
from .lnurl import (decode_lnurl, request_lnurl, callback_lnurl, LNURLError,
|
from .lnurl import (decode_lnurl, request_lnurl, callback_lnurl, LNURLError,
|
||||||
lightning_address_to_url, try_resolve_lnurlpay, LNURL6Data,
|
lightning_address_to_url, try_resolve_lnurlpay, LNURL6Data,
|
||||||
LNURL3Data, LNURLData)
|
LNURL3Data, LNURLData, SUPPORTED_LNURL_SCHEMES)
|
||||||
from .bitcoin import opcodes, construct_script
|
from .bitcoin import opcodes, construct_script
|
||||||
from .lnaddr import LnInvoiceException
|
from .lnaddr import LnInvoiceException
|
||||||
from .lnutil import IncompatibleOrInsaneFeatures
|
from .lnutil import IncompatibleOrInsaneFeatures
|
||||||
@@ -45,6 +45,22 @@ def remove_uri_prefix(data: str, *, prefix: str) -> str:
|
|||||||
return data
|
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_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_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_DOMAIN = r'\b([A-Za-z0-9-]+\.)+[A-Z|a-z]{2,7}\b'
|
||||||
@@ -98,6 +114,7 @@ class PaymentIdentifier(Logger):
|
|||||||
* openalias
|
* openalias
|
||||||
* bip21 URI
|
* bip21 URI
|
||||||
* lightning-URI (containing bolt11 or lnurl)
|
* lightning-URI (containing bolt11 or lnurl)
|
||||||
|
* lnurl-URI (lud17 lnurlw/lnurlp URI)
|
||||||
* bolt11 invoice
|
* bolt11 invoice
|
||||||
* lnurl
|
* lnurl
|
||||||
* lightning address
|
* lightning address
|
||||||
@@ -228,6 +245,10 @@ class PaymentIdentifier(Logger):
|
|||||||
self.logger.debug(f'Exception cause {e.args!r}')
|
self.logger.debug(f'Exception cause {e.args!r}')
|
||||||
return
|
return
|
||||||
self.set_state(PaymentIdentifierState.AVAILABLE)
|
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 + ':'):
|
elif text.lower().startswith(BITCOIN_BIP21_URI_SCHEME + ':'):
|
||||||
try:
|
try:
|
||||||
out = parse_bip21_URI(text)
|
out = parse_bip21_URI(text)
|
||||||
|
|||||||
@@ -184,6 +184,17 @@ class TestPaymentIdentifier(ElectrumTestCase):
|
|||||||
self.assertEqual(PaymentIdentifierType.LNURL, pi.type)
|
self.assertEqual(PaymentIdentifierType.LNURL, pi.type)
|
||||||
self.assertTrue(pi.need_resolve())
|
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')
|
@patch('electrum.payment_identifier.request_lnurl')
|
||||||
def test_lnurl_pay_resolve(self, mock_request_lnurl):
|
def test_lnurl_pay_resolve(self, mock_request_lnurl):
|
||||||
"""Test LNURL-pay (LNURL6) with mocked resolve"""
|
"""Test LNURL-pay (LNURL6) with mocked resolve"""
|
||||||
|
|||||||
Reference in New Issue
Block a user