Browse Source

Kivy: Support Lightning in Send tab

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
Janus 6 years ago
committed by ThomasV
parent
commit
1352b0ce9f
  1. 16
      electrum/gui/kivy/main_window.py
  2. 73
      electrum/gui/kivy/uix/screens.py
  3. 23
      electrum/gui/kivy/uix/ui_screens/send.kv
  4. 1
      electrum/lnworker.py

16
electrum/gui/kivy/main_window.py

@ -162,11 +162,16 @@ class ElectrumWindow(App):
self.switch_to('send') self.switch_to('send')
self.send_screen.set_URI(uri) self.send_screen.set_URI(uri)
def set_ln_invoice(self, invoice):
self.switch_to('send')
self.send_screen.set_ln_invoice(invoice)
def on_new_intent(self, intent): def on_new_intent(self, intent):
if intent.getScheme() != 'bitcoin': data = intent.getDataString()
return if intent.getScheme() == 'bitcoin':
uri = intent.getDataString() self.set_URI(data)
self.set_URI(uri) elif intent.getScheme() == 'lightning':
self.set_ln_invoice(data)
def on_language(self, instance, language): def on_language(self, instance, language):
Logger.info('language: {}'.format(language)) Logger.info('language: {}'.format(language))
@ -355,6 +360,9 @@ class ElectrumWindow(App):
if data.startswith('bitcoin:'): if data.startswith('bitcoin:'):
self.set_URI(data) self.set_URI(data)
return return
if data.startswith('ln'):
self.set_ln_invoice(data)
return
# try to decode transaction # try to decode transaction
from electrum.transaction import Transaction from electrum.transaction import Transaction
from electrum.util import bh2u from electrum.util import bh2u

73
electrum/gui/kivy/uix/screens.py

@ -1,8 +1,10 @@
import asyncio
from weakref import ref from weakref import ref
from decimal import Decimal from decimal import Decimal
import re import re
import datetime import datetime
import traceback, sys import traceback, sys
from enum import Enum, auto
from kivy.app import App from kivy.app import App
from kivy.cache import Cache from kivy.cache import Cache
@ -19,19 +21,26 @@ from kivy.factory import Factory
from kivy.utils import platform from kivy.utils import platform
from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
from electrum import bitcoin from electrum import bitcoin, constants
from electrum.transaction import TxOutput, Transaction, tx_from_str from electrum.transaction import TxOutput, Transaction, tx_from_str
from electrum.util import send_exception_to_crash_reporter, parse_URI, InvalidBitcoinURI from electrum.util import send_exception_to_crash_reporter, parse_URI, InvalidBitcoinURI
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.wallet import InternalAddressCorruption from electrum.wallet import InternalAddressCorruption
from electrum import simple_config from electrum import simple_config
from electrum.lnaddr import lndecode
from electrum.lnutil import RECEIVED, SENT
from .context_menu import ContextMenu from .context_menu import ContextMenu
from electrum.gui.kivy.i18n import _ from electrum.gui.kivy.i18n import _
class Destination(Enum):
Address = auto()
PR = auto()
LN = auto()
class HistoryRecycleView(RecycleView): class HistoryRecycleView(RecycleView):
pass pass
@ -184,7 +193,19 @@ class SendScreen(CScreen):
self.screen.message = uri.get('message', '') self.screen.message = uri.get('message', '')
self.screen.amount = self.app.format_amount_and_units(amount) if amount else '' self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
self.payment_request = None self.payment_request = None
self.screen.is_pr = False self.screen.destinationtype = Destination.Address
def set_ln_invoice(self, invoice):
try:
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
except Exception as e:
self.app.show_info(invoice + _(" is not a valid Lightning invoice: ") + repr(e)) # repr because str(Exception()) == ''
return
self.screen.address = invoice
self.screen.message = dict(lnaddr.tags).get('d', None)
self.screen.amount = self.app.format_amount_and_units(lnaddr.amount * bitcoin.COIN) if lnaddr.amount else ''
self.payment_request = None
self.screen.destinationtype = Destination.LN
def update(self): def update(self):
if self.app.wallet and self.payment_request_queued: if self.app.wallet and self.payment_request_queued:
@ -196,7 +217,7 @@ class SendScreen(CScreen):
self.screen.message = '' self.screen.message = ''
self.screen.address = '' self.screen.address = ''
self.payment_request = None self.payment_request = None
self.screen.is_pr = False self.screen.destinationtype = Destination.Address
def set_request(self, pr): def set_request(self, pr):
self.screen.address = pr.get_requestor() self.screen.address = pr.get_requestor()
@ -204,16 +225,16 @@ class SendScreen(CScreen):
self.screen.amount = self.app.format_amount_and_units(amount) if amount else '' self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
self.screen.message = pr.get_memo() self.screen.message = pr.get_memo()
if pr.is_pr(): if pr.is_pr():
self.screen.is_pr = True self.screen.destinationtype = Destination.PR
self.payment_request = pr self.payment_request = pr
else: else:
self.screen.is_pr = False self.screen.destinationtype = Destination.Address
self.payment_request = None self.payment_request = None
def do_save(self): def do_save(self):
if not self.screen.address: if not self.screen.address:
return return
if self.screen.is_pr: if self.screen.destinationtype == Destination.PR:
# it should be already saved # it should be already saved
return return
# save address as invoice # save address as invoice
@ -226,10 +247,10 @@ class SendScreen(CScreen):
self.app.wallet.invoices.add(pr) self.app.wallet.invoices.add(pr)
self.app.show_info(_("Invoice saved")) self.app.show_info(_("Invoice saved"))
if pr.is_pr(): if pr.is_pr():
self.screen.is_pr = True self.screen.destinationtype = Destination.PR
self.payment_request = pr self.payment_request = pr
else: else:
self.screen.is_pr = False self.screen.destinationtype = Destination.Address
self.payment_request = None self.payment_request = None
def do_paste(self): def do_paste(self):
@ -248,10 +269,42 @@ class SendScreen(CScreen):
self.app.tx_dialog(tx) self.app.tx_dialog(tx)
return return
# try to decode as URI/address # try to decode as URI/address
self.set_URI(data) if data.startswith('ln'):
self.set_ln_invoice(data.rstrip())
else:
self.set_URI(data)
def _do_send_lightning(self):
if not self.screen.amount:
self.app.show_error(_('Since the invoice contained no amount, you must enter one'))
return
invoice = self.screen.address
amount_sat = self.app.get_amount(self.screen.amount)
try:
addr = self.app.wallet.lnworker._check_invoice(invoice, amount_sat)
route = self.app.wallet.lnworker._create_route_from_invoice(decoded_invoice=addr)
except Exception as e:
self.app.show_error(_('Could not find path for payment. Check if you have open channels. Error details:') + ':\n' + repr(e))
self.app.network.register_callback(self.payment_completed_async_thread, ['ln_payment_completed'])
_addr, _peer, coro = self.app.wallet.lnworker._pay(invoice, amount_sat)
fut = asyncio.run_coroutine_threadsafe(coro, self.app.network.asyncio_loop)
fut.add_done_callback(self.ln_payment_result)
def payment_completed_async_thread(self, event, direction, htlc, preimage):
Clock.schedule_once(lambda dt: self.payment_completed(direction, htlc, preimage))
def payment_completed(self, direction, htlc, preimage):
self.app.show_info(_('Payment received') if direction == RECEIVED else _('Payment sent'))
def ln_payment_result(self, fut):
if fut.exception():
self.app.show_error(_('Lightning payment failed:') + '\n' + repr(fut.exception()))
def do_send(self): def do_send(self):
if self.screen.is_pr: if self.screen.destinationtype == Destination.LN:
self._do_send_lightning()
return
elif self.screen.destinationtype == Destination.PR:
if self.payment_request.has_expired(): if self.payment_request.has_expired():
self.app.show_error(_('Payment request has expired')) self.app.show_error(_('Payment request has expired'))
return return

23
electrum/gui/kivy/uix/ui_screens/send.kv

@ -1,4 +1,5 @@
#:import _ electrum.gui.kivy.i18n._ #:import _ electrum.gui.kivy.i18n._
#:import Destination electrum.gui.kivy.uix.screens.Destination
#:import Decimal decimal.Decimal #:import Decimal decimal.Decimal
#:set btc_symbol chr(171) #:set btc_symbol chr(171)
#:set mbtc_symbol chr(187) #:set mbtc_symbol chr(187)
@ -11,7 +12,7 @@ SendScreen:
address: '' address: ''
amount: '' amount: ''
message: '' message: ''
is_pr: False destinationtype: Destination.Address
BoxLayout BoxLayout
padding: '12dp', '12dp', '12dp', '12dp' padding: '12dp', '12dp', '12dp', '12dp'
spacing: '12dp' spacing: '12dp'
@ -36,7 +37,7 @@ SendScreen:
on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the recipient address using the Paste button, or use the camera to scan a QR code.'))) on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the recipient address using the Paste button, or use the camera to scan a QR code.')))
#on_release: Clock.schedule_once(lambda dt: app.popup_dialog('contacts')) #on_release: Clock.schedule_once(lambda dt: app.popup_dialog('contacts'))
CardSeparator: CardSeparator:
opacity: int(not root.is_pr) opacity: int(root.destinationtype == Destination.Address)
color: blue_bottom.foreground_color color: blue_bottom.foreground_color
BoxLayout: BoxLayout:
size_hint: 1, None size_hint: 1, None
@ -52,10 +53,10 @@ SendScreen:
id: amount_e id: amount_e
default_text: _('Amount') default_text: _('Amount')
text: s.amount if s.amount else _('Amount') text: s.amount if s.amount else _('Amount')
disabled: root.is_pr disabled: root.destinationtype == Destination.PR or root.destinationtype == Destination.LN and not s.amount
on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, True)) on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, True))
CardSeparator: CardSeparator:
opacity: int(not root.is_pr) opacity: int(root.destinationtype == Destination.Address)
color: blue_bottom.foreground_color color: blue_bottom.foreground_color
BoxLayout: BoxLayout:
id: message_selection id: message_selection
@ -69,27 +70,27 @@ SendScreen:
pos_hint: {'center_y': .5} pos_hint: {'center_y': .5}
BlueButton: BlueButton:
id: description id: description
text: s.message if s.message else (_('No Description') if root.is_pr else _('Description')) text: s.message if s.message else ({Destination.LN: _('Lightning invoice contains no description'), Destination.Address: _('Description'), Destination.PR: _('No Description')}[root.destinationtype])
disabled: root.is_pr disabled: root.destinationtype != Destination.Address
on_release: Clock.schedule_once(lambda dt: app.description_dialog(s)) on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
CardSeparator: CardSeparator:
opacity: int(not root.is_pr) opacity: int(root.destinationtype == Destination.Address)
color: blue_bottom.foreground_color color: blue_bottom.foreground_color
BoxLayout: BoxLayout:
size_hint: 1, None size_hint: 1, None
height: blue_bottom.item_height height: blue_bottom.item_height if root.destinationtype != Destination.LN else 0
spacing: '5dp' spacing: '5dp'
Image: Image:
source: 'atlas://electrum/gui/kivy/theming/light/star_big_inactive' source: 'atlas://electrum/gui/kivy/theming/light/star_big_inactive'
opacity: 0.7 opacity: 0.7 if root.destinationtype != Destination.LN else 0
size_hint: None, None size_hint: None, None
size: '22dp', '22dp' size: '22dp', '22dp'
pos_hint: {'center_y': .5} pos_hint: {'center_y': .5}
BlueButton: BlueButton:
id: fee_e id: fee_e
default_text: _('Fee') default_text: _('Fee')
text: app.fee_status text: app.fee_status if root.destinationtype != Destination.LN else ''
on_release: Clock.schedule_once(lambda dt: app.fee_dialog(s, True)) on_release: Clock.schedule_once(lambda dt: app.fee_dialog(s, True)) if root.destinationtype != Destination.LN else None
BoxLayout: BoxLayout:
size_hint: 1, None size_hint: 1, None
height: '48dp' height: '48dp'

1
electrum/lnworker.py

@ -97,6 +97,7 @@ class LNWorker(PrintError):
l.append((time.time(), direction, json.loads(encoder.encode(htlc)), bh2u(preimage))) l.append((time.time(), direction, json.loads(encoder.encode(htlc)), bh2u(preimage)))
self.wallet.storage.put('lightning_payments_completed', l) self.wallet.storage.put('lightning_payments_completed', l)
self.wallet.storage.write() self.wallet.storage.write()
self.network.trigger_callback('ln_payment_completed', direction, htlc, preimage)
def list_invoices(self): def list_invoices(self):
report = self._list_invoices() report = self._list_invoices()

Loading…
Cancel
Save