From 95439306df5ba5ee215d2510e7f2e7a5cfc8c387 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Thu, 7 May 2026 17:16:15 +0200 Subject: [PATCH] fix: make BIP21 URI scheme network-aware for BitcoinPurple Adds BIP21_URI_SCHEME to AbstractNet (default 'bitcoin'), overridden to 'btcp' in BitcoinPurple. All parse/create/scan paths now use constants.net.BIP21_URI_SCHEME so QR codes with btcp:... URIs are correctly recognised and generated on the Purple network. --- electrum/bip21.py | 7 ++++--- electrum/constants.py | 2 ++ electrum/gui/qml/qeqr.py | 3 ++- electrum/interface.py | 2 +- electrum/invoices.py | 2 +- electrum/payment_identifier.py | 3 ++- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/electrum/bip21.py b/electrum/bip21.py index 3db495e7b..7e7b9b1a6 100644 --- a/electrum/bip21.py +++ b/electrum/bip21.py @@ -5,12 +5,13 @@ from decimal import Decimal from typing import Optional from . import bitcoin +from . import constants from .util import format_satoshis_plain from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC from .bolt11 import decode_bolt11_invoice, BOLT11DecodeException # note: when checking against these, use .lower() to support case-insensitivity -BITCOIN_BIP21_URI_SCHEME = 'bitcoin' +BITCOIN_BIP21_URI_SCHEME = 'bitcoin' # kept for backward-compat imports LIGHTNING_URI_SCHEME = 'lightning' # note: URI scheme handler registrations are duplicated all over the codebase: @@ -36,7 +37,7 @@ def parse_bip21_URI(uri: str) -> dict: return {'address': uri} u = urllib.parse.urlparse(uri) - if u.scheme.lower() != BITCOIN_BIP21_URI_SCHEME: + if u.scheme.lower() != constants.net.BIP21_URI_SCHEME: raise InvalidBitcoinURI("Not a bitcoin URI") address = u.path @@ -127,7 +128,7 @@ def create_bip21_uri(addr, amount_sat: Optional[int], message: Optional[str], v = urllib.parse.quote(v) query.append(f"{k}={v}") p = urllib.parse.ParseResult( - scheme=BITCOIN_BIP21_URI_SCHEME, + scheme=constants.net.BIP21_URI_SCHEME, netloc='', path=addr, params='', diff --git a/electrum/constants.py b/electrum/constants.py index e128ac10b..2c194d4cb 100644 --- a/electrum/constants.py +++ b/electrum/constants.py @@ -83,6 +83,7 @@ class AbstractNet: COIN_SYMBOL: str = "BTC" COIN_NAME: str = "Bitcoin" + BIP21_URI_SCHEME: str = "bitcoin" # PoW difficulty parameters (Bitcoin defaults; override per chain as needed) DIFFICULTY_ADJUSTMENT_INTERVAL: int = 2016 # blocks per retarget window @@ -283,6 +284,7 @@ class BitcoinPurple(AbstractNet): ADDRTYPE_P2SH = 55 SEGWIT_HRP = "btcp" BOLT11_HRP = SEGWIT_HRP + BIP21_URI_SCHEME = "btcp" GENESIS = "000003823fbf82ea4906cbe214617ce7a70a5da29c19ecb1d65618bcf04ec015" DEFAULT_PORTS = {'t': '50001', 's': '50002'} BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS = 0 diff --git a/electrum/gui/qml/qeqr.py b/electrum/gui/qml/qeqr.py index f7c20af59..76b5c708a 100644 --- a/electrum/gui/qml/qeqr.py +++ b/electrum/gui/qml/qeqr.py @@ -16,6 +16,7 @@ except ImportError: # Note: missing QtMultimedia will lead to errors when using QR scanner on desktop from PyQt6.QtCore import QObject as QVideoSink +from electrum import constants from electrum.logging import get_logger from electrum.qrreader import get_qr_reader from electrum.i18n import _ @@ -144,7 +145,7 @@ class QEQRImageProvider(QQuickImageProvider): # (unknown schemes might be found when a colon is in a serialized TX, which # leads to mangling of the tx, so we check for supported schemes.) uri = urllib.parse.urlparse(qstr) - if uri.scheme and uri.scheme in ['bitcoin', 'lightning']: + if uri.scheme and uri.scheme in [constants.net.BIP21_URI_SCHEME, 'lightning']: # urlencode request parameters query = urllib.parse.parse_qs(uri.query) query = urllib.parse.urlencode(query, doseq=True, quote_via=urllib.parse.quote) diff --git a/electrum/interface.py b/electrum/interface.py index 7f0af68bd..b8a2df4ce 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -1562,7 +1562,7 @@ class Interface(Logger): return '' if not isinstance(res, str): raise RequestCorrupted(f'{res!r} should be a str') - address = res.removeprefix('bitcoin:') + address = res.removeprefix(constants.net.BIP21_URI_SCHEME + ':') if not bitcoin.is_address(address): # note: do not hard-fail -- allow server to use future-type # bitcoin address we do not recognize diff --git a/electrum/invoices.py b/electrum/invoices.py index c15f32926..243339f77 100644 --- a/electrum/invoices.py +++ b/electrum/invoices.py @@ -341,7 +341,7 @@ class Request(BaseInvoice): if lightning_invoice: extra['lightning'] = lightning_invoice if not addr and lightning_invoice: - return "bitcoin:?lightning="+lightning_invoice + return f"{constants.net.BIP21_URI_SCHEME}:?lightning=" + lightning_invoice if not addr and not lightning_invoice: return None uri = create_bip21_uri(addr, amount, message, extra_query_params=extra) diff --git a/electrum/payment_identifier.py b/electrum/payment_identifier.py index 7fe8a2f1e..70fe0dfce 100644 --- a/electrum/payment_identifier.py +++ b/electrum/payment_identifier.py @@ -7,6 +7,7 @@ from enum import IntEnum from typing import NamedTuple, Optional, Callable, List, TYPE_CHECKING, Tuple, Union from . import bitcoin +from . import constants from .contacts import AliasNotFoundException from .i18n import _ from .invoices import Invoice @@ -249,7 +250,7 @@ class PaymentIdentifier(Logger): 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(constants.net.BIP21_URI_SCHEME + ':'): try: out = parse_bip21_URI(text) except InvalidBitcoinURI as e: