payment_identifier: factor out bip21 functions to bip21.py to break cyclic dependencies,
parse bolt11 only once, store invoice internally instead of bolt11 string add is_onchain method to indicate if payment identifier can be paid onchain
This commit is contained in:
127
electrum/bip21.py
Normal file
127
electrum/bip21.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import urllib
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from . import bitcoin
|
||||
from .util import format_satoshis_plain
|
||||
from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
|
||||
from .lnaddr import lndecode, LnDecodeException
|
||||
|
||||
# note: when checking against these, use .lower() to support case-insensitivity
|
||||
BITCOIN_BIP21_URI_SCHEME = 'bitcoin'
|
||||
LIGHTNING_URI_SCHEME = 'lightning'
|
||||
|
||||
|
||||
class InvalidBitcoinURI(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def parse_bip21_URI(uri: str) -> dict:
|
||||
"""Raises InvalidBitcoinURI on malformed URI."""
|
||||
|
||||
if not isinstance(uri, str):
|
||||
raise InvalidBitcoinURI(f"expected string, not {repr(uri)}")
|
||||
|
||||
if ':' not in uri:
|
||||
if not bitcoin.is_address(uri):
|
||||
raise InvalidBitcoinURI("Not a bitcoin address")
|
||||
return {'address': uri}
|
||||
|
||||
u = urllib.parse.urlparse(uri)
|
||||
if u.scheme.lower() != BITCOIN_BIP21_URI_SCHEME:
|
||||
raise InvalidBitcoinURI("Not a bitcoin URI")
|
||||
address = u.path
|
||||
|
||||
# python for android fails to parse query
|
||||
if address.find('?') > 0:
|
||||
address, query = u.path.split('?')
|
||||
pq = urllib.parse.parse_qs(query)
|
||||
else:
|
||||
pq = urllib.parse.parse_qs(u.query)
|
||||
|
||||
for k, v in pq.items():
|
||||
if len(v) != 1:
|
||||
raise InvalidBitcoinURI(f'Duplicate Key: {repr(k)}')
|
||||
|
||||
out = {k: v[0] for k, v in pq.items()}
|
||||
if address:
|
||||
if not bitcoin.is_address(address):
|
||||
raise InvalidBitcoinURI(f"Invalid bitcoin address: {address}")
|
||||
out['address'] = address
|
||||
if 'amount' in out:
|
||||
am = out['amount']
|
||||
try:
|
||||
m = re.match(r'([0-9.]+)X([0-9])', am)
|
||||
if m:
|
||||
k = int(m.group(2)) - 8
|
||||
amount = Decimal(m.group(1)) * pow(Decimal(10), k)
|
||||
else:
|
||||
amount = Decimal(am) * COIN
|
||||
if amount > TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN:
|
||||
raise InvalidBitcoinURI(f"amount is out-of-bounds: {amount!r} BTC")
|
||||
out['amount'] = int(amount)
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'amount' field: {repr(e)}") from e
|
||||
if 'message' in out:
|
||||
out['message'] = out['message']
|
||||
out['memo'] = out['message']
|
||||
if 'time' in out:
|
||||
try:
|
||||
out['time'] = int(out['time'])
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'time' field: {repr(e)}") from e
|
||||
if 'exp' in out:
|
||||
try:
|
||||
out['exp'] = int(out['exp'])
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'exp' field: {repr(e)}") from e
|
||||
if 'sig' in out:
|
||||
try:
|
||||
out['sig'] = bitcoin.base_decode(out['sig'], base=58).hex()
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'sig' field: {repr(e)}") from e
|
||||
if 'lightning' in out:
|
||||
try:
|
||||
lnaddr = lndecode(out['lightning'])
|
||||
except LnDecodeException as e:
|
||||
raise InvalidBitcoinURI(f"Failed to decode 'lightning' field: {e!r}") from e
|
||||
amount_sat = out.get('amount')
|
||||
if amount_sat:
|
||||
# allow small leeway due to msat precision
|
||||
if abs(amount_sat - int(lnaddr.get_amount_sat())) > 1:
|
||||
raise InvalidBitcoinURI("Inconsistent lightning field in bip21: amount")
|
||||
address = out.get('address')
|
||||
ln_fallback_addr = lnaddr.get_fallback_address()
|
||||
if address and ln_fallback_addr:
|
||||
if ln_fallback_addr != address:
|
||||
raise InvalidBitcoinURI("Inconsistent lightning field in bip21: address")
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def create_bip21_uri(addr, amount_sat: Optional[int], message: Optional[str],
|
||||
*, extra_query_params: Optional[dict] = None) -> str:
|
||||
if not bitcoin.is_address(addr):
|
||||
return ""
|
||||
if extra_query_params is None:
|
||||
extra_query_params = {}
|
||||
query = []
|
||||
if amount_sat:
|
||||
query.append('amount=%s' % format_satoshis_plain(amount_sat))
|
||||
if message:
|
||||
query.append('message=%s' % urllib.parse.quote(message))
|
||||
for k, v in extra_query_params.items():
|
||||
if not isinstance(k, str) or k != urllib.parse.quote(k):
|
||||
raise Exception(f"illegal key for URI: {repr(k)}")
|
||||
v = urllib.parse.quote(v)
|
||||
query.append(f"{k}={v}")
|
||||
p = urllib.parse.ParseResult(
|
||||
scheme=BITCOIN_BIP21_URI_SCHEME,
|
||||
netloc='',
|
||||
path=addr,
|
||||
params='',
|
||||
query='&'.join(query),
|
||||
fragment=''
|
||||
)
|
||||
return str(urllib.parse.urlunparse(p))
|
||||
@@ -87,7 +87,7 @@ Label.register(
|
||||
|
||||
|
||||
from electrum.util import NoDynamicFeeEstimates, NotEnoughFunds, UserFacingException
|
||||
from electrum.payment_identifier import BITCOIN_BIP21_URI_SCHEME, LIGHTNING_URI_SCHEME
|
||||
from electrum.bip21 import BITCOIN_BIP21_URI_SCHEME, LIGHTNING_URI_SCHEME
|
||||
|
||||
from .uix.dialogs.lightning_open_channel import LightningOpenChannelDialog
|
||||
from .uix.dialogs.lightning_channels import LightningChannelsDialog, SwapDialog
|
||||
|
||||
@@ -19,7 +19,8 @@ from electrum import bitcoin, constants
|
||||
from electrum import lnutil
|
||||
from electrum.transaction import tx_from_any, PartialTxOutput
|
||||
from electrum.util import TxMinedInfo, InvoiceError, format_time, parse_max_spend
|
||||
from electrum.payment_identifier import parse_bip21_URI, BITCOIN_BIP21_URI_SCHEME, maybe_extract_lightning_payment_identifier, InvalidBitcoinURI
|
||||
from electrum.bip21 import BITCOIN_BIP21_URI_SCHEME, parse_bip21_URI, InvalidBitcoinURI
|
||||
from electrum.payment_identifier import maybe_extract_lightning_payment_identifier
|
||||
from electrum.lnaddr import lndecode, LnInvoiceException
|
||||
from electrum.lnurl import decode_lnurl, request_lnurl, callback_lnurl, LNURLError, LNURL6Data
|
||||
from electrum.logging import Logger
|
||||
|
||||
@@ -16,7 +16,7 @@ from PyQt5.QtQml import qmlRegisterType, qmlRegisterUncreatableType, QQmlApplica
|
||||
from electrum import version, constants
|
||||
from electrum.i18n import _
|
||||
from electrum.logging import Logger, get_logger
|
||||
from electrum.payment_identifier import BITCOIN_BIP21_URI_SCHEME, LIGHTNING_URI_SCHEME
|
||||
from electrum.bip21 import BITCOIN_BIP21_URI_SCHEME, LIGHTNING_URI_SCHEME
|
||||
from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue
|
||||
from electrum.network import Network
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ from electrum.lnutil import format_short_channel_id, IncompatibleOrInsaneFeature
|
||||
from electrum.lnurl import decode_lnurl, request_lnurl, callback_lnurl
|
||||
from electrum.bitcoin import COIN
|
||||
from electrum.paymentrequest import PaymentRequest
|
||||
from electrum.payment_identifier import (parse_bip21_URI, InvalidBitcoinURI, maybe_extract_lightning_payment_identifier,
|
||||
from electrum.payment_identifier import (maybe_extract_lightning_payment_identifier,
|
||||
PaymentIdentifier, PaymentIdentifierState, PaymentIdentifierType)
|
||||
|
||||
from electrum.bip21 import parse_bip21_URI, InvalidBitcoinURI
|
||||
from .qetypes import QEAmount
|
||||
from .qewallet import QEWallet
|
||||
from .util import status_update_timer_interval, QtEventListener, event_listener
|
||||
@@ -526,7 +526,7 @@ class QEInvoiceParser(QEInvoice):
|
||||
self.validationSuccess.emit()
|
||||
return
|
||||
elif self._pi.type == PaymentIdentifierType.BOLT11:
|
||||
lninvoice = Invoice.from_bech32(self._pi.bolt11)
|
||||
lninvoice = self._pi.bolt11
|
||||
if not self._wallet.wallet.has_lightning() and not lninvoice.get_address():
|
||||
self.validationError.emit('no_lightning',
|
||||
_('Detected valid Lightning invoice, but Lightning not enabled for wallet and no fallback address found.'))
|
||||
@@ -539,7 +539,7 @@ class QEInvoiceParser(QEInvoice):
|
||||
self.validationSuccess.emit()
|
||||
elif self._pi.type == PaymentIdentifierType.BIP21:
|
||||
if self._wallet.wallet.has_lightning() and self._wallet.wallet.lnworker.channels and self._pi.bolt11:
|
||||
lninvoice = Invoice.from_bech32(self._pi.bolt11)
|
||||
lninvoice = self._pi.bolt11
|
||||
self.setValidLightningInvoice(lninvoice)
|
||||
self.validationSuccess.emit()
|
||||
else:
|
||||
|
||||
@@ -57,7 +57,8 @@ from electrum.i18n import _
|
||||
from electrum.util import (format_time, UserCancelled, profiler, bfh, InvalidPassword,
|
||||
UserFacingException, get_new_wallet_name, send_exception_to_crash_reporter,
|
||||
AddTransactionException, os_chmod)
|
||||
from electrum.payment_identifier import BITCOIN_BIP21_URI_SCHEME, PaymentIdentifier
|
||||
from electrum.bip21 import BITCOIN_BIP21_URI_SCHEME
|
||||
from electrum.payment_identifier import PaymentIdentifier
|
||||
from electrum.invoices import PR_PAID, Invoice
|
||||
from electrum.transaction import (Transaction, PartialTxInput,
|
||||
PartialTransaction, PartialTxOutput)
|
||||
|
||||
@@ -7,7 +7,7 @@ import attr
|
||||
from .json_db import StoredObject, stored_in
|
||||
from .i18n import _
|
||||
from .util import age, InvoiceError, format_satoshis
|
||||
from .payment_identifier import create_bip21_uri
|
||||
from .bip21 import create_bip21_uri
|
||||
from .lnutil import hex_to_bytes
|
||||
from .lnaddr import lndecode, LnAddr
|
||||
from . import constants
|
||||
|
||||
@@ -9,14 +9,16 @@ from typing import NamedTuple, Optional, Callable, List, TYPE_CHECKING, Tuple
|
||||
from . import bitcoin
|
||||
from .contacts import AliasNotFoundException
|
||||
from .i18n import _
|
||||
from .invoices import Invoice
|
||||
from .logging import Logger
|
||||
from .util import parse_max_spend, format_satoshis_plain
|
||||
from .util import parse_max_spend, format_satoshis_plain, InvoiceError
|
||||
from .util import get_asyncio_loop, log_exceptions
|
||||
from .transaction import PartialTxOutput
|
||||
from .lnurl import decode_lnurl, request_lnurl, callback_lnurl, LNURLError, lightning_address_to_url
|
||||
from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, opcodes, construct_script
|
||||
from .lnaddr import lndecode, LnDecodeException, LnInvoiceException
|
||||
from .lnutil import IncompatibleOrInsaneFeatures
|
||||
from .bip21 import parse_bip21_URI, InvalidBitcoinURI, LIGHTNING_URI_SCHEME, BITCOIN_BIP21_URI_SCHEME
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .wallet import Abstract_Wallet
|
||||
@@ -34,125 +36,6 @@ def maybe_extract_lightning_payment_identifier(data: str) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
# note: when checking against these, use .lower() to support case-insensitivity
|
||||
BITCOIN_BIP21_URI_SCHEME = 'bitcoin'
|
||||
LIGHTNING_URI_SCHEME = 'lightning'
|
||||
|
||||
|
||||
class InvalidBitcoinURI(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def parse_bip21_URI(uri: str) -> dict:
|
||||
"""Raises InvalidBitcoinURI on malformed URI."""
|
||||
|
||||
if not isinstance(uri, str):
|
||||
raise InvalidBitcoinURI(f"expected string, not {repr(uri)}")
|
||||
|
||||
if ':' not in uri:
|
||||
if not bitcoin.is_address(uri):
|
||||
raise InvalidBitcoinURI("Not a bitcoin address")
|
||||
return {'address': uri}
|
||||
|
||||
u = urllib.parse.urlparse(uri)
|
||||
if u.scheme.lower() != BITCOIN_BIP21_URI_SCHEME:
|
||||
raise InvalidBitcoinURI("Not a bitcoin URI")
|
||||
address = u.path
|
||||
|
||||
# python for android fails to parse query
|
||||
if address.find('?') > 0:
|
||||
address, query = u.path.split('?')
|
||||
pq = urllib.parse.parse_qs(query)
|
||||
else:
|
||||
pq = urllib.parse.parse_qs(u.query)
|
||||
|
||||
for k, v in pq.items():
|
||||
if len(v) != 1:
|
||||
raise InvalidBitcoinURI(f'Duplicate Key: {repr(k)}')
|
||||
|
||||
out = {k: v[0] for k, v in pq.items()}
|
||||
if address:
|
||||
if not bitcoin.is_address(address):
|
||||
raise InvalidBitcoinURI(f"Invalid bitcoin address: {address}")
|
||||
out['address'] = address
|
||||
if 'amount' in out:
|
||||
am = out['amount']
|
||||
try:
|
||||
m = re.match(r'([0-9.]+)X([0-9])', am)
|
||||
if m:
|
||||
k = int(m.group(2)) - 8
|
||||
amount = Decimal(m.group(1)) * pow(Decimal(10), k)
|
||||
else:
|
||||
amount = Decimal(am) * COIN
|
||||
if amount > TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN:
|
||||
raise InvalidBitcoinURI(f"amount is out-of-bounds: {amount!r} BTC")
|
||||
out['amount'] = int(amount)
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'amount' field: {repr(e)}") from e
|
||||
if 'message' in out:
|
||||
out['message'] = out['message']
|
||||
out['memo'] = out['message']
|
||||
if 'time' in out:
|
||||
try:
|
||||
out['time'] = int(out['time'])
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'time' field: {repr(e)}") from e
|
||||
if 'exp' in out:
|
||||
try:
|
||||
out['exp'] = int(out['exp'])
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'exp' field: {repr(e)}") from e
|
||||
if 'sig' in out:
|
||||
try:
|
||||
out['sig'] = bitcoin.base_decode(out['sig'], base=58).hex()
|
||||
except Exception as e:
|
||||
raise InvalidBitcoinURI(f"failed to parse 'sig' field: {repr(e)}") from e
|
||||
if 'lightning' in out:
|
||||
try:
|
||||
lnaddr = lndecode(out['lightning'])
|
||||
except LnDecodeException as e:
|
||||
raise InvalidBitcoinURI(f"Failed to decode 'lightning' field: {e!r}") from e
|
||||
amount_sat = out.get('amount')
|
||||
if amount_sat:
|
||||
# allow small leeway due to msat precision
|
||||
if abs(amount_sat - int(lnaddr.get_amount_sat())) > 1:
|
||||
raise InvalidBitcoinURI("Inconsistent lightning field in bip21: amount")
|
||||
address = out.get('address')
|
||||
ln_fallback_addr = lnaddr.get_fallback_address()
|
||||
if address and ln_fallback_addr:
|
||||
if ln_fallback_addr != address:
|
||||
raise InvalidBitcoinURI("Inconsistent lightning field in bip21: address")
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def create_bip21_uri(addr, amount_sat: Optional[int], message: Optional[str],
|
||||
*, extra_query_params: Optional[dict] = None) -> str:
|
||||
if not bitcoin.is_address(addr):
|
||||
return ""
|
||||
if extra_query_params is None:
|
||||
extra_query_params = {}
|
||||
query = []
|
||||
if amount_sat:
|
||||
query.append('amount=%s' % format_satoshis_plain(amount_sat))
|
||||
if message:
|
||||
query.append('message=%s' % urllib.parse.quote(message))
|
||||
for k, v in extra_query_params.items():
|
||||
if not isinstance(k, str) or k != urllib.parse.quote(k):
|
||||
raise Exception(f"illegal key for URI: {repr(k)}")
|
||||
v = urllib.parse.quote(v)
|
||||
query.append(f"{k}={v}")
|
||||
p = urllib.parse.ParseResult(
|
||||
scheme=BITCOIN_BIP21_URI_SCHEME,
|
||||
netloc='',
|
||||
path=addr,
|
||||
params='',
|
||||
query='&'.join(query),
|
||||
fragment=''
|
||||
)
|
||||
return str(urllib.parse.urlunparse(p))
|
||||
|
||||
|
||||
def is_uri(data: str) -> bool:
|
||||
data = data.lower()
|
||||
if (data.startswith(LIGHTNING_URI_SCHEME + ":") or
|
||||
@@ -279,7 +162,14 @@ class PaymentIdentifier(Logger):
|
||||
return self._state in [PaymentIdentifierState.AVAILABLE]
|
||||
|
||||
def is_lightning(self):
|
||||
return self.lnurl or self.bolt11
|
||||
return bool(self.lnurl) or bool(self.bolt11)
|
||||
|
||||
def is_onchain(self):
|
||||
if self._type in [PaymentIdentifierType.SPK, PaymentIdentifierType.MULTILINE, PaymentIdentifierType.BIP70,
|
||||
PaymentIdentifierType.OPENALIAS]:
|
||||
return True
|
||||
if self._type in [PaymentIdentifierType.LNURLP, PaymentIdentifierType.BOLT11, PaymentIdentifierType.LNADDR]:
|
||||
return bool(self.bolt11) and bool(self.bolt11.get_address())
|
||||
|
||||
def is_multiline(self):
|
||||
return bool(self.multiline_outputs)
|
||||
@@ -293,8 +183,7 @@ class PaymentIdentifier(Logger):
|
||||
elif self._type == PaymentIdentifierType.BIP70:
|
||||
return not self.need_resolve() # always fixed after resolve?
|
||||
elif self._type == PaymentIdentifierType.BOLT11:
|
||||
lnaddr = lndecode(self.bolt11)
|
||||
return bool(lnaddr.amount)
|
||||
return bool(self.bolt11.get_amount_sat())
|
||||
elif self._type in [PaymentIdentifierType.LNURLP, PaymentIdentifierType.LNADDR]:
|
||||
# amount limits known after resolve, might be specific amount or locked to range
|
||||
if self.need_resolve():
|
||||
@@ -339,16 +228,12 @@ class PaymentIdentifier(Logger):
|
||||
else:
|
||||
self._type = PaymentIdentifierType.BOLT11
|
||||
try:
|
||||
lndecode(invoice_or_lnurl)
|
||||
except LnInvoiceException as e:
|
||||
self.error = _("Error parsing Lightning invoice") + f":\n{e}"
|
||||
self.bolt11 = Invoice.from_bech32(invoice_or_lnurl)
|
||||
except InvoiceError as e:
|
||||
self.error = self._get_error_from_invoiceerror(e)
|
||||
self.set_state(PaymentIdentifierState.INVALID)
|
||||
self.logger.debug(f'Exception cause {e.args!r}')
|
||||
return
|
||||
except IncompatibleOrInsaneFeatures as e:
|
||||
self.error = _("Invoice requires unknown or incompatible Lightning feature") + f":\n{e!r}"
|
||||
self.set_state(PaymentIdentifierState.INVALID)
|
||||
return
|
||||
self.bolt11 = invoice_or_lnurl
|
||||
self.set_state(PaymentIdentifierState.AVAILABLE)
|
||||
elif text.lower().startswith(BITCOIN_BIP21_URI_SCHEME + ':'):
|
||||
try:
|
||||
@@ -643,6 +528,16 @@ class PaymentIdentifier(Logger):
|
||||
assert bitcoin.is_address(address)
|
||||
return address
|
||||
|
||||
def _get_error_from_invoiceerror(self, e: 'InvoiceError') -> str:
|
||||
error = _("Error parsing Lightning invoice") + f":\n{e!r}"
|
||||
if e.args and len(e.args):
|
||||
arg = e.args[0]
|
||||
if isinstance(arg, LnInvoiceException):
|
||||
error = _("Error parsing Lightning invoice") + f":\n{e}"
|
||||
elif isinstance(arg, IncompatibleOrInsaneFeatures):
|
||||
error = _("Invoice requires unknown or incompatible Lightning feature") + f":\n{e!r}"
|
||||
return error
|
||||
|
||||
def get_fields_for_GUI(self) -> FieldsForGUI:
|
||||
recipient = None
|
||||
amount = None
|
||||
@@ -662,8 +557,8 @@ class PaymentIdentifier(Logger):
|
||||
self.warning = _('WARNING: the alias "{}" could not be validated via an additional '
|
||||
'security check, DNSSEC, and thus may not be correct.').format(key)
|
||||
|
||||
elif self.bolt11 and self.wallet.has_lightning():
|
||||
recipient, amount, description = self._get_bolt11_fields(self.bolt11)
|
||||
elif self.bolt11:
|
||||
recipient, amount, description = self._get_bolt11_fields()
|
||||
|
||||
elif self.lnurl and self.lnurl_data:
|
||||
domain = urllib.parse.urlparse(self.lnurl).netloc
|
||||
@@ -705,9 +600,8 @@ class PaymentIdentifier(Logger):
|
||||
return FieldsForGUI(recipient=recipient, amount=amount, description=description,
|
||||
comment=comment, validated=validated, amount_range=amount_range)
|
||||
|
||||
def _get_bolt11_fields(self, bolt11_invoice):
|
||||
"""Parse ln invoice, and prepare the send tab for it."""
|
||||
lnaddr = lndecode(bolt11_invoice) #
|
||||
def _get_bolt11_fields(self):
|
||||
lnaddr = self.bolt11._lnaddr # TODO: improve access to lnaddr
|
||||
pubkey = lnaddr.pubkey.serialize().hex()
|
||||
for k, v in lnaddr.tags:
|
||||
if k == 'd':
|
||||
@@ -740,20 +634,17 @@ class PaymentIdentifier(Logger):
|
||||
if self.bip70:
|
||||
return self.bip70_data.has_expired()
|
||||
elif self.bolt11:
|
||||
lnaddr = lndecode(self.bolt11)
|
||||
return lnaddr.is_expired()
|
||||
return self.bolt11.has_expired()
|
||||
elif self.bip21:
|
||||
expires = self.bip21.get('exp') + self.bip21.get('time') if self.bip21.get('exp') else 0
|
||||
return bool(expires) and expires < time.time()
|
||||
return False
|
||||
|
||||
def get_invoice(self, amount_sat, message):
|
||||
from .invoices import Invoice
|
||||
if self.is_lightning():
|
||||
invoice_str = self.bolt11
|
||||
if not invoice_str:
|
||||
invoice = self.bolt11
|
||||
if not invoice:
|
||||
return
|
||||
invoice = Invoice.from_bech32(invoice_str)
|
||||
if invoice.amount_msat is None:
|
||||
invoice.amount_msat = int(amount_sat * 1000)
|
||||
return invoice
|
||||
|
||||
@@ -5,7 +5,7 @@ from electrum import util
|
||||
from electrum.util import (format_satoshis, format_fee_satoshis, is_hash256_str, chunks, is_ip_address,
|
||||
list_enabled_bits, format_satoshis_plain, is_private_netaddress, is_hex_str,
|
||||
is_integer, is_non_negative_integer, is_int_or_float, is_non_negative_int_or_float)
|
||||
from electrum.payment_identifier import parse_bip21_URI, InvalidBitcoinURI
|
||||
from electrum.bip21 import parse_bip21_URI, InvalidBitcoinURI
|
||||
from . import ElectrumTestCase, as_testnet
|
||||
|
||||
|
||||
|
||||
@@ -42,8 +42,7 @@ import copy
|
||||
|
||||
from . import ecc, bitcoin, constants, segwit_addr, bip32
|
||||
from .bip32 import BIP32Node
|
||||
from .util import profiler, to_bytes, bfh, chunks, is_hex_str
|
||||
from .payment_identifier import parse_max_spend
|
||||
from .util import profiler, to_bytes, bfh, chunks, is_hex_str, parse_max_spend
|
||||
from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
|
||||
hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr,
|
||||
var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
|
||||
|
||||
@@ -58,7 +58,6 @@ from .util import (NotEnoughFunds, UserCancelled, profiler, OldTaskGroup, ignore
|
||||
WalletFileException, BitcoinException,
|
||||
InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
|
||||
Fiat, bfh, TxMinedInfo, quantize_feerate, OrderedDictWithIndex)
|
||||
from .payment_identifier import create_bip21_uri, parse_max_spend
|
||||
from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
|
||||
from .bitcoin import COIN, TYPE_ADDRESS
|
||||
from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold
|
||||
@@ -66,7 +65,7 @@ from .crypto import sha256d
|
||||
from . import keystore
|
||||
from .keystore import (load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK,
|
||||
AddressIndexGeneric, CannotDerivePubkey)
|
||||
from .util import multisig_type
|
||||
from .util import multisig_type, parse_max_spend
|
||||
from .storage import StorageEncryptionVersion, WalletStorage
|
||||
from .wallet_db import WalletDB
|
||||
from . import transaction, bitcoin, coinchooser, paymentrequest, ecc, bip32
|
||||
|
||||
Reference in New Issue
Block a user