Browse Source

payment_identifier: add DOMAINLIKE payment identifier type, support domainlike -> openalias

master
Sander van Grieken 3 years ago
parent
commit
fbb37d6fae
  1. 2
      electrum/gui/qt/paytoedit.py
  2. 8
      electrum/gui/qt/send_tab.py
  3. 40
      electrum/payment_identifier.py

2
electrum/gui/qt/paytoedit.py

@ -216,7 +216,7 @@ class PayToEdit(QObject, Logger, GenericInputHandler):
# pushback timer if timer active or PI needs resolving
pi = PaymentIdentifier(self.send_tab.wallet, self.text_edit.toPlainText())
if pi.need_resolve() or self.edit_timer.isActive():
if not pi.is_valid() or pi.need_resolve() or self.edit_timer.isActive():
self.edit_timer.start()
else:
self.set_payment_identifier(self.text_edit.toPlainText())

8
electrum/gui/qt/send_tab.py

@ -363,7 +363,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
if pi.is_multiline():
self.lock_fields(lock_recipient=False, lock_amount=True, lock_max=True, lock_description=False)
self.set_field_style(self.payto_e, True if not pi.is_valid() else None, False)
self.set_field_validated(self.payto_e, validated=pi.is_valid()) # TODO: validated used differently here than openalias
self.save_button.setEnabled(pi.is_valid())
self.send_button.setEnabled(pi.is_valid())
self.payto_e.setToolTip(pi.get_error() if not pi.is_valid() else '')
@ -378,11 +378,13 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
return
lock_recipient = pi.type != PaymentIdentifierType.SPK \
and not (pi.type == PaymentIdentifierType.EMAILLIKE and pi.state in [PaymentIdentifierState.NOT_FOUND,PaymentIdentifierState.NEED_RESOLVE])
and not (pi.type in [PaymentIdentifierType.EMAILLIKE, PaymentIdentifierType.DOMAINLIKE] \
and pi.state in [PaymentIdentifierState.NOT_FOUND, PaymentIdentifierState.NEED_RESOLVE])
lock_amount = pi.is_amount_locked()
lock_max = lock_amount \
or pi.type in [PaymentIdentifierType.BOLT11, PaymentIdentifierType.LNURLP,
PaymentIdentifierType.LNADDR, PaymentIdentifierType.EMAILLIKE]
PaymentIdentifierType.LNADDR, PaymentIdentifierType.EMAILLIKE,
PaymentIdentifierType.DOMAINLIKE]
self.lock_fields(lock_recipient=lock_recipient,
lock_amount=lock_amount,

40
electrum/payment_identifier.py

@ -162,7 +162,8 @@ def is_uri(data: str) -> bool:
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'
class PaymentIdentifierState(IntEnum):
@ -194,6 +195,7 @@ class PaymentIdentifierType(IntEnum):
EMAILLIKE = 7
OPENALIAS = 8
LNADDR = 9
DOMAINLIKE = 10
class FieldsForGUI(NamedTuple):
@ -235,6 +237,7 @@ class PaymentIdentifier(Logger):
self.spk = None
#
self.emaillike = None
self.domainlike = None
self.openalias_data = None
#
self.bip70 = None
@ -284,9 +287,7 @@ class PaymentIdentifier(Logger):
return self.is_multiline() and self._is_max
def is_amount_locked(self):
if self._type == PaymentIdentifierType.SPK:
return False
elif self._type == PaymentIdentifierType.BIP21:
if self._type == PaymentIdentifierType.BIP21:
return bool(self.bip21.get('amount'))
elif self._type == PaymentIdentifierType.BIP70:
return True # TODO always given?
@ -303,9 +304,7 @@ class PaymentIdentifier(Logger):
return True
elif self._type == PaymentIdentifierType.MULTILINE:
return True
elif self._type == PaymentIdentifierType.EMAILLIKE:
return False
elif self._type == PaymentIdentifierType.OPENALIAS:
else:
return False
def is_error(self) -> bool:
@ -384,6 +383,10 @@ class PaymentIdentifier(Logger):
self._type = PaymentIdentifierType.EMAILLIKE
self.emaillike = text
self.set_state(PaymentIdentifierState.NEED_RESOLVE)
elif re.match(RE_DOMAIN, text):
self._type = PaymentIdentifierType.DOMAINLIKE
self.domainlike = text
self.set_state(PaymentIdentifierState.NEED_RESOLVE)
elif self.error is None:
truncated_text = f"{text[:100]}..." if len(text) > 100 else text
self.error = f"Unknown payment identifier:\n{truncated_text}"
@ -397,7 +400,7 @@ class PaymentIdentifier(Logger):
@log_exceptions
async def _do_resolve(self, *, on_finished=None):
try:
if self.emaillike:
if self.emaillike or self.domainlike:
# TODO: parallel lookup?
data = await self.resolve_openalias()
if data:
@ -405,11 +408,12 @@ class PaymentIdentifier(Logger):
self.logger.debug(f'OA: {data!r}')
name = data.get('name')
address = data.get('address')
self.contacts[self.emaillike] = ('openalias', name)
key = self.emaillike if self.emaillike else self.domainlike
self.contacts[key] = ('openalias', name)
if not data.get('validated'):
self.warning = _(
'WARNING: the alias "{}" could not be validated via an additional '
'security check, DNSSEC, and thus may not be correct.').format(self.emaillike)
'security check, DNSSEC, and thus may not be correct.').format(key)
try:
assert bitcoin.is_address(address)
scriptpubkey = bytes.fromhex(bitcoin.address_to_script(address))
@ -419,7 +423,7 @@ class PaymentIdentifier(Logger):
except Exception as e:
self.error = str(e)
self.set_state(PaymentIdentifierState.NOT_FOUND)
else:
elif self.emaillike:
lnurl = lightning_address_to_url(self.emaillike)
try:
data = await request_lnurl(lnurl)
@ -433,6 +437,8 @@ class PaymentIdentifier(Logger):
# NOTE: any other exception is swallowed here (e.g. DNS error)
# as the user may be typing and we have an incomplete emaillike
self.set_state(PaymentIdentifierState.NOT_FOUND)
else:
self.set_state(PaymentIdentifierState.NOT_FOUND)
elif self.bip70:
from . import paymentrequest
data = await paymentrequest.get_payment_request(self.bip70)
@ -627,14 +633,16 @@ class PaymentIdentifier(Logger):
validated = None
comment = None
if self.emaillike and self.openalias_data:
if (self.emaillike or self.domainlike) and self.openalias_data:
key = self.emaillike if self.emaillike else self.domainlike
address = self.openalias_data.get('address')
name = self.openalias_data.get('name')
recipient = self.emaillike + ' <' + address + '>'
description = name
recipient = key + ' <' + address + '>'
validated = self.openalias_data.get('validated')
if not validated:
self.warning = _('WARNING: the alias "{}" could not be validated via an additional '
'security check, DNSSEC, and thus may not be correct.').format(self.emaillike)
'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)
@ -689,8 +697,8 @@ class PaymentIdentifier(Logger):
return pubkey, amount, description
async def resolve_openalias(self) -> Optional[dict]:
key = self.emaillike
# TODO: below check needed? we already matched RE_EMAIL
key = self.emaillike if self.emaillike else self.domainlike
# TODO: below check needed? we already matched RE_EMAIL/RE_DOMAIN
# if not (('.' in key) and ('<' not in key) and (' ' not in key)):
# return None
parts = key.split(sep=',') # assuming single line

Loading…
Cancel
Save