URI: 
       tmain_window.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tmain_window.py (54019B)
       ---
            1 import re
            2 import os
            3 import sys
            4 import time
            5 import datetime
            6 import traceback
            7 from decimal import Decimal
            8 import threading
            9 import asyncio
           10 from typing import TYPE_CHECKING, Optional, Union, Callable, Sequence
           11 
           12 from electrum.storage import WalletStorage, StorageReadWriteError
           13 from electrum.wallet_db import WalletDB
           14 from electrum.wallet import Wallet, InternalAddressCorruption, Abstract_Wallet
           15 from electrum.wallet import check_password_for_directory, update_password_for_directory
           16 
           17 from electrum.plugin import run_hook
           18 from electrum import util
           19 from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
           20                            format_satoshis, format_satoshis_plain, format_fee_satoshis,
           21                            maybe_extract_bolt11_invoice)
           22 from electrum.invoices import PR_PAID, PR_FAILED
           23 from electrum import blockchain
           24 from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed
           25 from electrum.interface import PREFERRED_NETWORK_PROTOCOL, ServerAddr
           26 from electrum.logging import Logger
           27 from .i18n import _
           28 from . import KIVY_GUI_PATH
           29 
           30 from kivy.app import App
           31 from kivy.core.window import Window
           32 from kivy.utils import platform
           33 from kivy.properties import (OptionProperty, AliasProperty, ObjectProperty,
           34                              StringProperty, ListProperty, BooleanProperty, NumericProperty)
           35 from kivy.cache import Cache
           36 from kivy.clock import Clock
           37 from kivy.factory import Factory
           38 from kivy.metrics import inch
           39 from kivy.lang import Builder
           40 from .uix.dialogs.password_dialog import OpenWalletDialog, ChangePasswordDialog, PincodeDialog
           41 
           42 ## lazy imports for factory so that widgets can be used in kv
           43 #Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard')
           44 #Factory.register('InfoBubble', module='electrum.gui.kivy.uix.dialogs')
           45 #Factory.register('OutputList', module='electrum.gui.kivy.uix.dialogs')
           46 #Factory.register('OutputItem', module='electrum.gui.kivy.uix.dialogs')
           47 
           48 from .uix.dialogs.installwizard import InstallWizard
           49 from .uix.dialogs import InfoBubble, crash_reporter
           50 from .uix.dialogs import OutputList, OutputItem
           51 from .uix.dialogs import TopLabel, RefLabel
           52 from .uix.dialogs.question import Question
           53 
           54 #from kivy.core.window import Window
           55 #Window.softinput_mode = 'below_target'
           56 
           57 # delayed imports: for startup speed on android
           58 notification = app = ref = None
           59 
           60 # register widget cache for keeping memory down timeout to forever to cache
           61 # the data
           62 Cache.register('electrum_widgets', timeout=0)
           63 
           64 from kivy.uix.screenmanager import Screen
           65 from kivy.uix.tabbedpanel import TabbedPanel
           66 from kivy.uix.label import Label
           67 from kivy.core.clipboard import Clipboard
           68 
           69 Factory.register('TabbedCarousel', module='electrum.gui.kivy.uix.screens')
           70 
           71 # Register fonts without this you won't be able to use bold/italic...
           72 # inside markup.
           73 from kivy.core.text import Label
           74 Label.register(
           75     'Roboto',
           76     KIVY_GUI_PATH + '/data/fonts/Roboto.ttf',
           77     KIVY_GUI_PATH + '/data/fonts/Roboto.ttf',
           78     KIVY_GUI_PATH + '/data/fonts/Roboto-Bold.ttf',
           79     KIVY_GUI_PATH + '/data/fonts/Roboto-Bold.ttf',
           80 )
           81 
           82 
           83 from electrum.util import (NoDynamicFeeEstimates, NotEnoughFunds,
           84                            BITCOIN_BIP21_URI_SCHEME, LIGHTNING_URI_SCHEME,
           85                            UserFacingException)
           86 
           87 from .uix.dialogs.lightning_open_channel import LightningOpenChannelDialog
           88 from .uix.dialogs.lightning_channels import LightningChannelsDialog, SwapDialog
           89 
           90 if TYPE_CHECKING:
           91     from . import ElectrumGui
           92     from electrum.simple_config import SimpleConfig
           93     from electrum.plugin import Plugins
           94     from electrum.paymentrequest import PaymentRequest
           95 
           96 
           97 class ElectrumWindow(App, Logger):
           98 
           99     electrum_config = ObjectProperty(None)
          100     language = StringProperty('en')
          101 
          102     # properties might be updated by the network
          103     num_blocks = NumericProperty(0)
          104     num_nodes = NumericProperty(0)
          105     server_host = StringProperty('')
          106     server_port = StringProperty('')
          107     num_chains = NumericProperty(0)
          108     blockchain_name = StringProperty('')
          109     fee_status = StringProperty('Fee')
          110     balance = StringProperty('')
          111     fiat_balance = StringProperty('')
          112     is_fiat = BooleanProperty(False)
          113     blockchain_forkpoint = NumericProperty(0)
          114 
          115     lightning_gossip_num_peers = NumericProperty(0)
          116     lightning_gossip_num_nodes = NumericProperty(0)
          117     lightning_gossip_num_channels = NumericProperty(0)
          118     lightning_gossip_num_queries = NumericProperty(0)
          119 
          120     auto_connect = BooleanProperty(False)
          121     def on_auto_connect(self, instance, x):
          122         net_params = self.network.get_parameters()
          123         net_params = net_params._replace(auto_connect=self.auto_connect)
          124         self.network.run_from_another_thread(self.network.set_parameters(net_params))
          125     def toggle_auto_connect(self, x):
          126         self.auto_connect = not self.auto_connect
          127 
          128     oneserver = BooleanProperty(False)
          129     def on_oneserver(self, instance, x):
          130         net_params = self.network.get_parameters()
          131         net_params = net_params._replace(oneserver=self.oneserver)
          132         self.network.run_from_another_thread(self.network.set_parameters(net_params))
          133     def toggle_oneserver(self, x):
          134         self.oneserver = not self.oneserver
          135 
          136     proxy_str = StringProperty('')
          137     def update_proxy_str(self, proxy: dict):
          138         mode = proxy.get('mode')
          139         host = proxy.get('host')
          140         port = proxy.get('port')
          141         self.proxy_str = (host + ':' + port) if mode else _('None')
          142 
          143     def choose_server_dialog(self, popup):
          144         from .uix.dialogs.choice_dialog import ChoiceDialog
          145         protocol = PREFERRED_NETWORK_PROTOCOL
          146         def cb2(server_str):
          147             popup.ids.server_str.text = server_str
          148         servers = self.network.get_servers()
          149         server_choices = {}
          150         for _host, d in sorted(servers.items()):
          151             port = d.get(protocol)
          152             if port:
          153                 server = ServerAddr(_host, port, protocol=protocol)
          154                 server_choices[server.net_addr_str()] = _host
          155         ChoiceDialog(_('Choose a server'), server_choices, popup.ids.server_str.text, cb2).open()
          156 
          157     def maybe_switch_to_server(self, server_str: str):
          158         net_params = self.network.get_parameters()
          159         try:
          160             server = ServerAddr.from_str_with_inference(server_str)
          161             if not server: raise Exception("failed to parse")
          162         except Exception as e:
          163             self.show_error(_("Invalid server details: {}").format(repr(e)))
          164             return
          165         net_params = net_params._replace(server=server)
          166         self.network.run_from_another_thread(self.network.set_parameters(net_params))
          167 
          168     def choose_blockchain_dialog(self, dt):
          169         from .uix.dialogs.choice_dialog import ChoiceDialog
          170         chains = self.network.get_blockchains()
          171         def cb(name):
          172             with blockchain.blockchains_lock: blockchain_items = list(blockchain.blockchains.items())
          173             for chain_id, b in blockchain_items:
          174                 if name == b.get_name():
          175                     self.network.run_from_another_thread(self.network.follow_chain_given_id(chain_id))
          176         chain_objects = [blockchain.blockchains.get(chain_id) for chain_id in chains]
          177         chain_objects = filter(lambda b: b is not None, chain_objects)
          178         names = [b.get_name() for b in chain_objects]
          179         if len(names) > 1:
          180             cur_chain = self.network.blockchain().get_name()
          181             ChoiceDialog(_('Choose your chain'), names, cur_chain, cb).open()
          182 
          183     use_rbf = BooleanProperty(False)
          184     def on_use_rbf(self, instance, x):
          185         self.electrum_config.set_key('use_rbf', self.use_rbf, True)
          186 
          187     use_gossip = BooleanProperty(False)
          188     def on_use_gossip(self, instance, x):
          189         self.electrum_config.set_key('use_gossip', self.use_gossip, True)
          190         if self.use_gossip:
          191             self.network.start_gossip()
          192         else:
          193             self.network.run_from_another_thread(
          194                 self.network.stop_gossip())
          195 
          196     android_backups = BooleanProperty(False)
          197     def on_android_backups(self, instance, x):
          198         self.electrum_config.set_key('android_backups', self.android_backups, True)
          199 
          200     use_change = BooleanProperty(False)
          201     def on_use_change(self, instance, x):
          202         if self.wallet:
          203             self.wallet.use_change = self.use_change
          204             self.wallet.db.put('use_change', self.use_change)
          205             self.wallet.save_db()
          206 
          207     use_unconfirmed = BooleanProperty(False)
          208     def on_use_unconfirmed(self, instance, x):
          209         self.electrum_config.set_key('confirmed_only', not self.use_unconfirmed, True)
          210 
          211     def switch_to_send_screen(func):
          212         # try until send_screen is available
          213         def wrapper(self, *args):
          214             f = lambda dt: (bool(func(self, *args) and False) if self.send_screen else bool(self.switch_to('send') or True)) if self.wallet else True
          215             Clock.schedule_interval(f, 0.1)
          216         return wrapper
          217 
          218     @switch_to_send_screen
          219     def set_URI(self, uri):
          220         self.send_screen.set_URI(uri)
          221 
          222     @switch_to_send_screen
          223     def set_ln_invoice(self, invoice):
          224         self.send_screen.set_ln_invoice(invoice)
          225 
          226     def on_new_intent(self, intent):
          227         data = str(intent.getDataString())
          228         scheme = str(intent.getScheme()).lower()
          229         if scheme == BITCOIN_BIP21_URI_SCHEME:
          230             self.set_URI(data)
          231         elif scheme == LIGHTNING_URI_SCHEME:
          232             self.set_ln_invoice(data)
          233 
          234     def on_language(self, instance, language):
          235         self.logger.info('language: {}'.format(language))
          236         _.switch_lang(language)
          237 
          238     def update_history(self, *dt):
          239         if self.history_screen:
          240             self.history_screen.update()
          241 
          242     def on_quotes(self, d):
          243         self.logger.info("on_quotes")
          244         self._trigger_update_status()
          245         self._trigger_update_history()
          246 
          247     def on_history(self, d):
          248         self.logger.info("on_history")
          249         if self.wallet:
          250             self.wallet.clear_coin_price_cache()
          251         self._trigger_update_history()
          252 
          253     def on_fee_histogram(self, *args):
          254         self._trigger_update_history()
          255 
          256     def on_request_status(self, event, wallet, key, status):
          257         req = self.wallet.receive_requests.get(key)
          258         if req is None:
          259             return
          260         if self.receive_screen:
          261             if status == PR_PAID:
          262                 self.receive_screen.update()
          263             else:
          264                 self.receive_screen.update_item(key, req)
          265         if self.request_popup and self.request_popup.key == key:
          266             self.request_popup.update_status()
          267         if status == PR_PAID:
          268             self.show_info(_('Payment Received') + '\n' + key)
          269             self._trigger_update_history()
          270 
          271     def on_invoice_status(self, event, wallet, key):
          272         req = self.wallet.get_invoice(key)
          273         if req is None:
          274             return
          275         status = self.wallet.get_invoice_status(req)
          276         if self.send_screen:
          277             if status == PR_PAID:
          278                 self.send_screen.update()
          279             else:
          280                 self.send_screen.update_item(key, req)
          281 
          282         if self.invoice_popup and self.invoice_popup.key == key:
          283             self.invoice_popup.update_status()
          284 
          285     def on_payment_succeeded(self, event, wallet, key):
          286         description = self.wallet.get_label(key)
          287         self.show_info(_('Payment succeeded') + '\n\n' + description)
          288         self._trigger_update_history()
          289 
          290     def on_payment_failed(self, event, wallet, key, reason):
          291         self.show_info(_('Payment failed') + '\n\n' + reason)
          292 
          293     def _get_bu(self):
          294         return self.electrum_config.get_base_unit()
          295 
          296     def _set_bu(self, value):
          297         self.electrum_config.set_base_unit(value)
          298         self._trigger_update_status()
          299         self._trigger_update_history()
          300 
          301     wallet_name = StringProperty(_('No Wallet'))
          302     base_unit = AliasProperty(_get_bu, _set_bu)
          303     fiat_unit = StringProperty('')
          304 
          305     def on_fiat_unit(self, a, b):
          306         self._trigger_update_history()
          307 
          308     def decimal_point(self):
          309         return self.electrum_config.get_decimal_point()
          310 
          311     def btc_to_fiat(self, amount_str):
          312         if not amount_str:
          313             return ''
          314         if not self.fx.is_enabled():
          315             return ''
          316         rate = self.fx.exchange_rate()
          317         if rate.is_nan():
          318             return ''
          319         fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8)
          320         return "{:.2f}".format(fiat_amount).rstrip('0').rstrip('.')
          321 
          322     def fiat_to_btc(self, fiat_amount):
          323         if not fiat_amount:
          324             return ''
          325         rate = self.fx.exchange_rate()
          326         if rate.is_nan():
          327             return ''
          328         satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate))
          329         return format_satoshis_plain(satoshis, decimal_point=self.decimal_point())
          330 
          331     def get_amount(self, amount_str):
          332         a, u = amount_str.split()
          333         assert u == self.base_unit
          334         try:
          335             x = Decimal(a)
          336         except:
          337             return None
          338         p = pow(10, self.decimal_point())
          339         return int(p * x)
          340 
          341 
          342     _orientation = OptionProperty('landscape',
          343                                  options=('landscape', 'portrait'))
          344 
          345     def _get_orientation(self):
          346         return self._orientation
          347 
          348     orientation = AliasProperty(_get_orientation,
          349                                 None,
          350                                 bind=('_orientation',))
          351     '''Tries to ascertain the kind of device the app is running on.
          352     Cane be one of `tablet` or `phone`.
          353 
          354     :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape'
          355     '''
          356 
          357     _ui_mode = OptionProperty('phone', options=('tablet', 'phone'))
          358 
          359     def _get_ui_mode(self):
          360         return self._ui_mode
          361 
          362     ui_mode = AliasProperty(_get_ui_mode,
          363                             None,
          364                             bind=('_ui_mode',))
          365     '''Defines tries to ascertain the kind of device the app is running on.
          366     Cane be one of `tablet` or `phone`.
          367 
          368     :data:`ui_mode` is a read only `AliasProperty` Defaults to 'phone'
          369     '''
          370 
          371     def __init__(self, **kwargs):
          372         # initialize variables
          373         self._clipboard = Clipboard
          374         self.info_bubble = None
          375         self.nfcscanner = None
          376         self.tabs = None
          377         self.is_exit = False
          378         self.wallet = None  # type: Optional[Abstract_Wallet]
          379         self.pause_time = 0
          380         self.asyncio_loop = asyncio.get_event_loop()
          381         self.password = None
          382         self._use_single_password = False
          383 
          384         App.__init__(self)#, **kwargs)
          385         Logger.__init__(self)
          386 
          387         self.electrum_config = config = kwargs.get('config', None)  # type: SimpleConfig
          388         self.language = config.get('language', 'en')
          389         self.network = network = kwargs.get('network', None)  # type: Network
          390         if self.network:
          391             self.num_blocks = self.network.get_local_height()
          392             self.num_nodes = len(self.network.get_interfaces())
          393             net_params = self.network.get_parameters()
          394             self.server_host = net_params.server.host
          395             self.server_port = str(net_params.server.port)
          396             self.auto_connect = net_params.auto_connect
          397             self.oneserver = net_params.oneserver
          398             self.proxy_config = net_params.proxy if net_params.proxy else {}
          399             self.update_proxy_str(self.proxy_config)
          400 
          401         self.plugins = kwargs.get('plugins', None)  # type: Plugins
          402         self.gui_object = kwargs.get('gui_object', None)  # type: ElectrumGui
          403         self.daemon = self.gui_object.daemon
          404         self.fx = self.daemon.fx
          405         self.use_rbf = config.get('use_rbf', True)
          406         self.android_backups = config.get('android_backups', False)
          407         self.use_gossip = config.get('use_gossip', False)
          408         self.use_unconfirmed = not config.get('confirmed_only', False)
          409 
          410         # create triggers so as to minimize updating a max of 2 times a sec
          411         self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5)
          412         self._trigger_update_status = Clock.create_trigger(self.update_status, .5)
          413         self._trigger_update_history = Clock.create_trigger(self.update_history, .5)
          414         self._trigger_update_interfaces = Clock.create_trigger(self.update_interfaces, .5)
          415 
          416         self._periodic_update_status_during_sync = Clock.schedule_interval(self.update_wallet_synchronizing_progress, .5)
          417 
          418         # cached dialogs
          419         self._settings_dialog = None
          420         self._channels_dialog = None
          421         self._addresses_dialog = None
          422         self.set_fee_status()
          423         self.invoice_popup = None
          424         self.request_popup = None
          425 
          426     def on_pr(self, pr: 'PaymentRequest'):
          427         if not self.wallet:
          428             self.show_error(_('No wallet loaded.'))
          429             return
          430         if pr.verify(self.wallet.contacts):
          431             key = pr.get_id()
          432             invoice = self.wallet.get_invoice(key)  # FIXME wrong key...
          433             if invoice and self.wallet.get_invoice_status(invoice) == PR_PAID:
          434                 self.show_error("invoice already paid")
          435                 self.send_screen.do_clear()
          436             elif pr.has_expired():
          437                 self.show_error(_('Payment request has expired'))
          438             else:
          439                 self.switch_to('send')
          440                 self.send_screen.set_request(pr)
          441         else:
          442             self.show_error("invoice error:" + pr.error)
          443             self.send_screen.do_clear()
          444 
          445     def on_qr(self, data):
          446         from electrum.bitcoin import is_address
          447         data = data.strip()
          448         if is_address(data):
          449             self.set_URI(data)
          450             return
          451         if data.lower().startswith(BITCOIN_BIP21_URI_SCHEME + ':'):
          452             self.set_URI(data)
          453             return
          454         if data.lower().startswith('channel_backup:'):
          455             self.import_channel_backup(data)
          456             return
          457         bolt11_invoice = maybe_extract_bolt11_invoice(data)
          458         if bolt11_invoice is not None:
          459             self.set_ln_invoice(bolt11_invoice)
          460             return
          461         # try to decode transaction
          462         from electrum.transaction import tx_from_any
          463         try:
          464             tx = tx_from_any(data)
          465         except:
          466             tx = None
          467         if tx:
          468             self.tx_dialog(tx)
          469             return
          470         # show error
          471         self.show_error("Unable to decode QR data")
          472 
          473     def update_tab(self, name):
          474         s = getattr(self, name + '_screen', None)
          475         if s:
          476             s.update()
          477 
          478     @profiler
          479     def update_tabs(self):
          480         for name in ['send', 'history', 'receive']:
          481             self.update_tab(name)
          482 
          483     def switch_to(self, name):
          484         s = getattr(self, name + '_screen', None)
          485         panel = self.tabs.ids.panel
          486         tab = self.tabs.ids[name + '_tab']
          487         panel.switch_to(tab)
          488 
          489     def show_request(self, is_lightning, key):
          490         from .uix.dialogs.request_dialog import RequestDialog
          491         self.request_popup = RequestDialog('Request', key)
          492         self.request_popup.open()
          493 
          494     def show_invoice(self, is_lightning, key):
          495         from .uix.dialogs.invoice_dialog import InvoiceDialog
          496         invoice = self.wallet.get_invoice(key)
          497         if not invoice:
          498             return
          499         data = invoice.invoice if is_lightning else key
          500         self.invoice_popup = InvoiceDialog('Invoice', data, key)
          501         self.invoice_popup.open()
          502 
          503     def qr_dialog(self, title, data, show_text=False, text_for_clipboard=None, help_text=None):
          504         from .uix.dialogs.qr_dialog import QRDialog
          505         def on_qr_failure():
          506             popup.dismiss()
          507             msg = _('Failed to display QR code.')
          508             if text_for_clipboard:
          509                 msg += '\n' + _('Text copied to clipboard.')
          510                 self._clipboard.copy(text_for_clipboard)
          511             Clock.schedule_once(lambda dt: self.show_info(msg))
          512         popup = QRDialog(
          513             title, data, show_text,
          514             failure_cb=on_qr_failure,
          515             text_for_clipboard=text_for_clipboard,
          516             help_text=help_text)
          517         popup.open()
          518 
          519     def scan_qr(self, on_complete):
          520         if platform != 'android':
          521             return self.scan_qr_non_android(on_complete)
          522         from jnius import autoclass, cast
          523         from android import activity
          524         PythonActivity = autoclass('org.kivy.android.PythonActivity')
          525         SimpleScannerActivity = autoclass("org.electrum.qr.SimpleScannerActivity")
          526         Intent = autoclass('android.content.Intent')
          527         intent = Intent(PythonActivity.mActivity, SimpleScannerActivity)
          528 
          529         def on_qr_result(requestCode, resultCode, intent):
          530             try:
          531                 if resultCode == -1:  # RESULT_OK:
          532                     #  this doesn't work due to some bug in jnius:
          533                     # contents = intent.getStringExtra("text")
          534                     String = autoclass("java.lang.String")
          535                     contents = intent.getStringExtra(String("text"))
          536                     on_complete(contents)
          537             except Exception as e:  # exc would otherwise get lost
          538                 send_exception_to_crash_reporter(e)
          539             finally:
          540                 activity.unbind(on_activity_result=on_qr_result)
          541         activity.bind(on_activity_result=on_qr_result)
          542         PythonActivity.mActivity.startActivityForResult(intent, 0)
          543 
          544     def scan_qr_non_android(self, on_complete):
          545         from electrum import qrscanner
          546         try:
          547             video_dev = self.electrum_config.get_video_device()
          548             data = qrscanner.scan_barcode(video_dev)
          549             on_complete(data)
          550         except UserFacingException as e:
          551             self.show_error(e)
          552         except BaseException as e:
          553             self.logger.exception('camera error')
          554             self.show_error(repr(e))
          555 
          556     def do_share(self, data, title):
          557         if platform != 'android':
          558             return
          559         from jnius import autoclass, cast
          560         JS = autoclass('java.lang.String')
          561         Intent = autoclass('android.content.Intent')
          562         sendIntent = Intent()
          563         sendIntent.setAction(Intent.ACTION_SEND)
          564         sendIntent.setType("text/plain")
          565         sendIntent.putExtra(Intent.EXTRA_TEXT, JS(data))
          566         PythonActivity = autoclass('org.kivy.android.PythonActivity')
          567         currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
          568         it = Intent.createChooser(sendIntent, cast('java.lang.CharSequence', JS(title)))
          569         currentActivity.startActivity(it)
          570 
          571     def build(self):
          572         return Builder.load_file(KIVY_GUI_PATH + '/main.kv')
          573 
          574     def _pause(self):
          575         if platform == 'android':
          576             # move activity to back
          577             from jnius import autoclass
          578             python_act = autoclass('org.kivy.android.PythonActivity')
          579             mActivity = python_act.mActivity
          580             mActivity.moveTaskToBack(True)
          581 
          582     def handle_crash_on_startup(func):
          583         def wrapper(self, *args, **kwargs):
          584             try:
          585                 return func(self, *args, **kwargs)
          586             except Exception as e:
          587                 self.logger.exception('crash on startup')
          588                 from .uix.dialogs.crash_reporter import CrashReporter
          589                 # show the crash reporter, and when it's closed, shutdown the app
          590                 cr = CrashReporter(self, exctype=type(e), value=e, tb=e.__traceback__)
          591                 cr.on_dismiss = lambda: self.stop()
          592                 Clock.schedule_once(lambda _, cr=cr: cr.open(), 0)
          593         return wrapper
          594 
          595     @handle_crash_on_startup
          596     def on_start(self):
          597         ''' This is the start point of the kivy ui
          598         '''
          599         import time
          600         self.logger.info('Time to on_start: {} <<<<<<<<'.format(time.process_time()))
          601         Window.bind(size=self.on_size, on_keyboard=self.on_keyboard)
          602         #Window.softinput_mode = 'below_target'
          603         self.on_size(Window, Window.size)
          604         self.init_ui()
          605         crash_reporter.ExceptionHook(self)
          606         # init plugins
          607         run_hook('init_kivy', self)
          608         # fiat currency
          609         self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''
          610         # default tab
          611         self.switch_to('history')
          612         # bind intent for bitcoin: URI scheme
          613         if platform == 'android':
          614             from android import activity
          615             from jnius import autoclass
          616             PythonActivity = autoclass('org.kivy.android.PythonActivity')
          617             mactivity = PythonActivity.mActivity
          618             self.on_new_intent(mactivity.getIntent())
          619             activity.bind(on_new_intent=self.on_new_intent)
          620         # connect callbacks
          621         if self.network:
          622             interests = ['wallet_updated', 'network_updated', 'blockchain_updated',
          623                          'status', 'new_transaction', 'verified']
          624             util.register_callback(self.on_network_event, interests)
          625             util.register_callback(self.on_fee, ['fee'])
          626             util.register_callback(self.on_fee_histogram, ['fee_histogram'])
          627             util.register_callback(self.on_quotes, ['on_quotes'])
          628             util.register_callback(self.on_history, ['on_history'])
          629             util.register_callback(self.on_channels, ['channels_updated'])
          630             util.register_callback(self.on_channel, ['channel'])
          631             util.register_callback(self.on_invoice_status, ['invoice_status'])
          632             util.register_callback(self.on_request_status, ['request_status'])
          633             util.register_callback(self.on_payment_failed, ['payment_failed'])
          634             util.register_callback(self.on_payment_succeeded, ['payment_succeeded'])
          635             util.register_callback(self.on_channel_db, ['channel_db'])
          636             util.register_callback(self.set_num_peers, ['gossip_peers'])
          637             util.register_callback(self.set_unknown_channels, ['unknown_channels'])
          638         # load wallet
          639         self.load_wallet_by_name(self.electrum_config.get_wallet_path(use_gui_last_wallet=True))
          640         # URI passed in config
          641         uri = self.electrum_config.get('url')
          642         if uri:
          643             self.set_URI(uri)
          644 
          645     def on_channel_db(self, event, num_nodes, num_channels, num_policies):
          646         self.lightning_gossip_num_nodes = num_nodes
          647         self.lightning_gossip_num_channels = num_channels
          648 
          649     def set_num_peers(self, event, num_peers):
          650         self.lightning_gossip_num_peers = num_peers
          651 
          652     def set_unknown_channels(self, event, unknown):
          653         self.lightning_gossip_num_queries = unknown
          654 
          655     def get_wallet_path(self):
          656         if self.wallet:
          657             return self.wallet.storage.path
          658         else:
          659             return ''
          660 
          661     def on_wizard_success(self, storage, db, password):
          662         self.password = password
          663         if self.electrum_config.get('single_password'):
          664             self._use_single_password = check_password_for_directory(self.electrum_config, password)
          665         self.logger.info(f'use single password: {self._use_single_password}')
          666         wallet = Wallet(db, storage, config=self.electrum_config)
          667         wallet.start_network(self.daemon.network)
          668         self.daemon.add_wallet(wallet)
          669         self.load_wallet(wallet)
          670 
          671     def on_wizard_aborted(self):
          672         # wizard did not return a wallet; and there is no wallet open atm
          673         if not self.wallet:
          674             self.stop()
          675 
          676     def load_wallet_by_name(self, path):
          677         if not path:
          678             return
          679         if self.wallet and self.wallet.storage.path == path:
          680             return
          681         if self.password and self._use_single_password:
          682             storage = WalletStorage(path)
          683             # call check_password to decrypt
          684             storage.check_password(self.password)
          685             self.on_open_wallet(self.password, storage)
          686             return
          687         d = OpenWalletDialog(self, path, self.on_open_wallet)
          688         d.open()
          689 
          690     def on_open_wallet(self, password, storage):
          691         if not storage.file_exists():
          692             wizard = InstallWizard(self.electrum_config, self.plugins)
          693             wizard.path = storage.path
          694             wizard.run('new')
          695         else:
          696             assert storage.is_past_initial_decryption()
          697             db = WalletDB(storage.read(), manual_upgrades=False)
          698             assert not db.requires_upgrade()
          699             self.on_wizard_success(storage, db, password)
          700 
          701     def on_stop(self):
          702         self.logger.info('on_stop')
          703         self.stop_wallet()
          704 
          705     def stop_wallet(self):
          706         if self.wallet:
          707             self.daemon.stop_wallet(self.wallet.storage.path)
          708             self.wallet = None
          709 
          710     def on_keyboard(self, instance, key, keycode, codepoint, modifiers):
          711         if key == 27 and self.is_exit is False:
          712             self.is_exit = True
          713             self.show_info(_('Press again to exit'))
          714             return True
          715         # override settings button
          716         if key in (319, 282): #f1/settings button on android
          717             #self.gui.main_gui.toggle_settings(self)
          718             return True
          719 
          720     def settings_dialog(self):
          721         from .uix.dialogs.settings import SettingsDialog
          722         if self._settings_dialog is None:
          723             self._settings_dialog = SettingsDialog(self)
          724         self._settings_dialog.update()
          725         self._settings_dialog.open()
          726 
          727     def lightning_open_channel_dialog(self):
          728         if not self.wallet.has_lightning():
          729             self.show_error(_('Lightning is not enabled for this wallet'))
          730             return
          731         if not self.wallet.lnworker.channels:
          732             warning1 = _("Lightning support in Electrum is experimental. "
          733                          "Do not put large amounts in lightning channels.")
          734             warning2 = _("Funds stored in lightning channels are not recoverable "
          735                          "from your seed. You must backup your wallet file everytime "
          736                          "you create a new channel.")
          737             d = Question(_('Do you want to create your first channel?') +
          738                          '\n\n' + warning1 + '\n\n' + warning2, self.open_channel_dialog_with_warning)
          739             d.open()
          740         else:
          741             d = LightningOpenChannelDialog(self)
          742             d.open()
          743 
          744     def swap_dialog(self):
          745         d = SwapDialog(self, self.electrum_config)
          746         d.open()
          747 
          748     def open_channel_dialog_with_warning(self, b):
          749         if b:
          750             d = LightningOpenChannelDialog(self)
          751             d.open()
          752 
          753     def lightning_channels_dialog(self):
          754         if self._channels_dialog is None:
          755             self._channels_dialog = LightningChannelsDialog(self)
          756         self._channels_dialog.open()
          757 
          758     def on_channel(self, evt, wallet, chan):
          759         if self._channels_dialog:
          760             Clock.schedule_once(lambda dt: self._channels_dialog.update())
          761 
          762     def on_channels(self, evt, wallet):
          763         if self._channels_dialog:
          764             Clock.schedule_once(lambda dt: self._channels_dialog.update())
          765 
          766     def is_wallet_creation_disabled(self):
          767         return bool(self.electrum_config.get('single_password')) and self.password is None
          768 
          769     def wallets_dialog(self):
          770         from .uix.dialogs.wallets import WalletDialog
          771         dirname = os.path.dirname(self.electrum_config.get_wallet_path())
          772         d = WalletDialog(dirname, self.load_wallet_by_name, self.is_wallet_creation_disabled())
          773         d.open()
          774 
          775     def popup_dialog(self, name):
          776         if name == 'settings':
          777             self.settings_dialog()
          778         elif name == 'wallets':
          779             self.wallets_dialog()
          780         elif name == 'status':
          781             popup = Builder.load_file(KIVY_GUI_PATH + f'/uix/ui_screens/{name}.kv')
          782             master_public_keys_layout = popup.ids.master_public_keys
          783             for xpub in self.wallet.get_master_public_keys()[1:]:
          784                 master_public_keys_layout.add_widget(TopLabel(text=_('Master Public Key')))
          785                 ref = RefLabel()
          786                 ref.name = _('Master Public Key')
          787                 ref.data = xpub
          788                 master_public_keys_layout.add_widget(ref)
          789             popup.open()
          790         elif name == 'lightning_channels_dialog' and not self.wallet.can_have_lightning():
          791             self.show_error(_("Not available for this wallet.") + "\n\n" +
          792                             _("Lightning is currently restricted to HD wallets with p2wpkh addresses."))
          793         elif name.endswith("_dialog"):
          794             getattr(self, name)()
          795         else:
          796             popup = Builder.load_file(KIVY_GUI_PATH + f'/uix/ui_screens/{name}.kv')
          797             popup.open()
          798 
          799     @profiler
          800     def init_ui(self):
          801         ''' Initialize The Ux part of electrum. This function performs the basic
          802         tasks of setting up the ui.
          803         '''
          804         #from weakref import ref
          805 
          806         self.funds_error = False
          807         # setup UX
          808         self.screens = {}
          809 
          810         #setup lazy imports for mainscreen
          811         Factory.register('AnimatedPopup',
          812                          module='electrum.gui.kivy.uix.dialogs')
          813         Factory.register('QRCodeWidget',
          814                          module='electrum.gui.kivy.uix.qrcodewidget')
          815 
          816         # preload widgets. Remove this if you want to load the widgets on demand
          817         #Cache.append('electrum_widgets', 'AnimatedPopup', Factory.AnimatedPopup())
          818         #Cache.append('electrum_widgets', 'QRCodeWidget', Factory.QRCodeWidget())
          819 
          820         # load and focus the ui
          821         self.root.manager = self.root.ids['manager']
          822 
          823         self.history_screen = None
          824         self.send_screen = None
          825         self.receive_screen = None
          826         self.icon = os.path.dirname(KIVY_GUI_PATH) + "/icons/electrum.png"
          827         self.tabs = self.root.ids['tabs']
          828 
          829     def update_interfaces(self, dt):
          830         net_params = self.network.get_parameters()
          831         self.num_nodes = len(self.network.get_interfaces())
          832         self.num_chains = len(self.network.get_blockchains())
          833         chain = self.network.blockchain()
          834         self.blockchain_forkpoint = chain.get_max_forkpoint()
          835         self.blockchain_name = chain.get_name()
          836         interface = self.network.interface
          837         if interface:
          838             self.server_host = interface.host
          839         else:
          840             self.server_host = str(net_params.server.host) + ' (connecting...)'
          841         self.proxy_config = net_params.proxy or {}
          842         self.update_proxy_str(self.proxy_config)
          843 
          844     def on_network_event(self, event, *args):
          845         self.logger.info('network event: '+ event)
          846         if event == 'network_updated':
          847             self._trigger_update_interfaces()
          848             self._trigger_update_status()
          849         elif event == 'wallet_updated':
          850             self._trigger_update_wallet()
          851             self._trigger_update_status()
          852         elif event == 'blockchain_updated':
          853             # to update number of confirmations in history
          854             self._trigger_update_wallet()
          855         elif event == 'status':
          856             self._trigger_update_status()
          857         elif event == 'new_transaction':
          858             self._trigger_update_wallet()
          859         elif event == 'verified':
          860             self._trigger_update_wallet()
          861 
          862     @profiler
          863     def load_wallet(self, wallet: 'Abstract_Wallet'):
          864         if self.wallet:
          865             self.stop_wallet()
          866         self.wallet = wallet
          867         self.wallet_name = wallet.basename()
          868         self.update_wallet()
          869         # Once GUI has been initialized check if we want to announce something
          870         # since the callback has been called before the GUI was initialized
          871         if self.receive_screen:
          872             self.receive_screen.clear()
          873         self.update_tabs()
          874         run_hook('load_wallet', wallet, self)
          875         try:
          876             wallet.try_detecting_internal_addresses_corruption()
          877         except InternalAddressCorruption as e:
          878             self.show_error(str(e))
          879             send_exception_to_crash_reporter(e)
          880             return
          881         self.use_change = self.wallet.use_change
          882         self.electrum_config.save_last_wallet(wallet)
          883         self.request_focus_for_main_view()
          884 
          885     def request_focus_for_main_view(self):
          886         if platform != 'android':
          887             return
          888         # The main view of the activity might be not have focus
          889         # in which case e.g. the OS "back" button would not work.
          890         # see #6276 (specifically "method 2" and "method 3")
          891         from jnius import autoclass
          892         PythonActivity = autoclass('org.kivy.android.PythonActivity')
          893         PythonActivity.requestFocusForMainView()
          894 
          895     def update_status(self, *dt):
          896         if not self.wallet:
          897             return
          898         if self.network is None or not self.network.is_connected():
          899             status = _("Offline")
          900         elif self.network.is_connected():
          901             self.num_blocks = self.network.get_local_height()
          902             server_height = self.network.get_server_height()
          903             server_lag = self.num_blocks - server_height
          904             if not self.wallet.up_to_date or server_height == 0:
          905                 num_sent, num_answered = self.wallet.get_history_sync_state_details()
          906                 status = ("{} [size=18dp]({}/{})[/size]"
          907                           .format(_("Synchronizing..."), num_answered, num_sent))
          908             elif server_lag > 1:
          909                 status = _("Server is lagging ({} blocks)").format(server_lag)
          910             else:
          911                 status = ''
          912         else:
          913             status = _("Disconnected")
          914         if status:
          915             self.balance = status
          916             self.fiat_balance = status
          917         else:
          918             c, u, x = self.wallet.get_balance()
          919             l = int(self.wallet.lnworker.get_balance()) if self.wallet.lnworker else 0
          920             balance_sat = c + u + x + l
          921             text = self.format_amount(balance_sat)
          922             self.balance = str(text.strip()) + ' [size=22dp]%s[/size]'% self.base_unit
          923             self.fiat_balance = self.fx.format_amount(balance_sat) + ' [size=22dp]%s[/size]'% self.fx.ccy
          924 
          925     def update_wallet_synchronizing_progress(self, *dt):
          926         if not self.wallet:
          927             return
          928         if not self.wallet.up_to_date:
          929             self._trigger_update_status()
          930 
          931     def get_max_amount(self):
          932         from electrum.transaction import PartialTxOutput
          933         if run_hook('abort_send', self):
          934             return ''
          935         inputs = self.wallet.get_spendable_coins(None)
          936         if not inputs:
          937             return ''
          938         addr = None
          939         if self.send_screen:
          940             addr = str(self.send_screen.address)
          941         if not addr:
          942             addr = self.wallet.dummy_address()
          943         outputs = [PartialTxOutput.from_address_and_value(addr, '!')]
          944         try:
          945             tx = self.wallet.make_unsigned_transaction(coins=inputs, outputs=outputs)
          946         except NoDynamicFeeEstimates as e:
          947             Clock.schedule_once(lambda dt, bound_e=e: self.show_error(str(bound_e)))
          948             return ''
          949         except NotEnoughFunds:
          950             return ''
          951         except InternalAddressCorruption as e:
          952             self.show_error(str(e))
          953             send_exception_to_crash_reporter(e)
          954             return ''
          955         amount = tx.output_value()
          956         __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
          957         amount_after_all_fees = amount - x_fee_amount
          958         return format_satoshis_plain(amount_after_all_fees, decimal_point=self.decimal_point())
          959 
          960     def format_amount(self, x, is_diff=False, whitespaces=False):
          961         return format_satoshis(
          962             x,
          963             num_zeros=0,
          964             decimal_point=self.decimal_point(),
          965             is_diff=is_diff,
          966             whitespaces=whitespaces,
          967         )
          968 
          969     def format_amount_and_units(self, x) -> str:
          970         if x is None:
          971             return 'none'
          972         if x == '!':
          973             return 'max'
          974         return format_satoshis_plain(x, decimal_point=self.decimal_point()) + ' ' + self.base_unit
          975 
          976     def format_fee_rate(self, fee_rate):
          977         # fee_rate is in sat/kB
          978         return format_fee_satoshis(fee_rate/1000) + ' sat/byte'
          979 
          980     #@profiler
          981     def update_wallet(self, *dt):
          982         self._trigger_update_status()
          983         if self.wallet and (self.wallet.up_to_date or not self.network or not self.network.is_connected()):
          984             self.update_tabs()
          985 
          986     def notify(self, message):
          987         try:
          988             global notification, os
          989             if not notification:
          990                 from plyer import notification
          991             icon = (os.path.dirname(os.path.realpath(__file__))
          992                     + '/../../' + self.icon)
          993             notification.notify('Electrum', message,
          994                             app_icon=icon, app_name='Electrum')
          995         except ImportError:
          996             self.logger.Error('Notification: needs plyer; `sudo python3 -m pip install plyer`')
          997 
          998     def on_pause(self):
          999         self.pause_time = time.time()
         1000         # pause nfc
         1001         if self.nfcscanner:
         1002             self.nfcscanner.nfc_disable()
         1003         return True
         1004 
         1005     def on_resume(self):
         1006         now = time.time()
         1007         if self.wallet and self.has_pin_code() and now - self.pause_time > 5*60:
         1008             d = PincodeDialog(
         1009                 self,
         1010                 check_password=self.check_pin_code,
         1011                 on_success=None,
         1012                 on_failure=self.stop)
         1013             d.open()
         1014         if self.nfcscanner:
         1015             self.nfcscanner.nfc_enable()
         1016 
         1017     def on_size(self, instance, value):
         1018         width, height = value
         1019         self._orientation = 'landscape' if width > height else 'portrait'
         1020         self._ui_mode = 'tablet' if min(width, height) > inch(3.51) else 'phone'
         1021 
         1022     def on_ref_label(self, label, *, show_text_with_qr: bool = True):
         1023         if not label.data:
         1024             return
         1025         self.qr_dialog(label.name, label.data, show_text_with_qr)
         1026 
         1027     def show_error(self, error, width='200dp', pos=None, arrow_pos=None,
         1028                    exit=False, icon=f'atlas://{KIVY_GUI_PATH}/theming/light/error', duration=0,
         1029                    modal=False):
         1030         ''' Show an error Message Bubble.
         1031         '''
         1032         self.show_info_bubble( text=error, icon=icon, width=width,
         1033             pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit,
         1034             duration=duration, modal=modal)
         1035 
         1036     def show_info(self, error, width='200dp', pos=None, arrow_pos=None,
         1037                   exit=False, duration=0, modal=False):
         1038         ''' Show an Info Message Bubble.
         1039         '''
         1040         self.show_error(error, icon=f'atlas://{KIVY_GUI_PATH}/theming/light/important',
         1041             duration=duration, modal=modal, exit=exit, pos=pos,
         1042             arrow_pos=arrow_pos)
         1043 
         1044     def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0,
         1045                          arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False):
         1046         '''Method to show an Information Bubble
         1047 
         1048         .. parameters::
         1049             text: Message to be displayed
         1050             pos: position for the bubble
         1051             duration: duration the bubble remains on screen. 0 = click to hide
         1052             width: width of the Bubble
         1053             arrow_pos: arrow position for the bubble
         1054         '''
         1055         text = str(text)  # so that we also handle e.g. Exception
         1056         info_bubble = self.info_bubble
         1057         if not info_bubble:
         1058             info_bubble = self.info_bubble = Factory.InfoBubble()
         1059 
         1060         win = Window
         1061         if info_bubble.parent:
         1062             win.remove_widget(info_bubble
         1063                                  if not info_bubble.modal else
         1064                                  info_bubble._modal_view)
         1065 
         1066         if not arrow_pos:
         1067             info_bubble.show_arrow = False
         1068         else:
         1069             info_bubble.show_arrow = True
         1070             info_bubble.arrow_pos = arrow_pos
         1071         img = info_bubble.ids.img
         1072         if text == 'texture':
         1073             # icon holds a texture not a source image
         1074             # display the texture in full screen
         1075             text = ''
         1076             img.texture = icon
         1077             info_bubble.fs = True
         1078             info_bubble.show_arrow = False
         1079             img.allow_stretch = True
         1080             info_bubble.dim_background = True
         1081             info_bubble.background_image = f'atlas://{KIVY_GUI_PATH}/theming/light/card'
         1082         else:
         1083             info_bubble.fs = False
         1084             info_bubble.icon = icon
         1085             #if img.texture and img._coreimage:
         1086             #    img.reload()
         1087             img.allow_stretch = False
         1088             info_bubble.dim_background = False
         1089             info_bubble.background_image = 'atlas://data/images/defaulttheme/bubble'
         1090         info_bubble.message = text
         1091         if not pos:
         1092             pos = (win.center[0], win.center[1] - (info_bubble.height/2))
         1093         info_bubble.show(pos, duration, width, modal=modal, exit=exit)
         1094 
         1095     def tx_dialog(self, tx):
         1096         from .uix.dialogs.tx_dialog import TxDialog
         1097         d = TxDialog(self, tx)
         1098         d.open()
         1099 
         1100     def show_transaction(self, txid):
         1101         tx = self.wallet.db.get_transaction(txid)
         1102         if not tx and self.wallet.lnworker:
         1103             tx = self.wallet.lnworker.lnwatcher.db.get_transaction(txid)
         1104         if tx:
         1105             self.tx_dialog(tx)
         1106         else:
         1107             self.show_error(f'Transaction not found {txid}')
         1108 
         1109     def lightning_tx_dialog(self, tx):
         1110         from .uix.dialogs.lightning_tx_dialog import LightningTxDialog
         1111         d = LightningTxDialog(self, tx)
         1112         d.open()
         1113 
         1114     def sign_tx(self, *args):
         1115         threading.Thread(target=self._sign_tx, args=args).start()
         1116 
         1117     def _sign_tx(self, tx, password, on_success, on_failure):
         1118         try:
         1119             self.wallet.sign_transaction(tx, password)
         1120         except InvalidPassword:
         1121             Clock.schedule_once(lambda dt: on_failure(_("Invalid PIN")))
         1122             return
         1123         on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
         1124         Clock.schedule_once(lambda dt: on_success(tx))
         1125 
         1126     def _broadcast_thread(self, tx, on_complete):
         1127         status = False
         1128         try:
         1129             self.network.run_from_another_thread(self.network.broadcast_transaction(tx))
         1130         except TxBroadcastError as e:
         1131             msg = e.get_message_for_gui()
         1132         except BestEffortRequestFailed as e:
         1133             msg = repr(e)
         1134         else:
         1135             status, msg = True, tx.txid()
         1136         Clock.schedule_once(lambda dt: on_complete(status, msg))
         1137 
         1138     def broadcast(self, tx):
         1139         def on_complete(ok, msg):
         1140             if ok:
         1141                 self.show_info(_('Payment sent.'))
         1142                 if self.send_screen:
         1143                     self.send_screen.do_clear()
         1144             else:
         1145                 msg = msg or ''
         1146                 self.show_error(msg)
         1147 
         1148         if self.network and self.network.is_connected():
         1149             self.show_info(_('Sending'))
         1150             threading.Thread(target=self._broadcast_thread, args=(tx, on_complete)).start()
         1151         else:
         1152             self.show_info(_('Cannot broadcast transaction') + ':\n' + _('Not connected'))
         1153 
         1154     def description_dialog(self, screen):
         1155         from .uix.dialogs.label_dialog import LabelDialog
         1156         text = screen.message
         1157         def callback(text):
         1158             screen.message = text
         1159         d = LabelDialog(_('Enter description'), text, callback)
         1160         d.open()
         1161 
         1162     def amount_dialog(self, screen, show_max):
         1163         from .uix.dialogs.amount_dialog import AmountDialog
         1164         amount = screen.amount
         1165         if amount:
         1166             amount, u = str(amount).split()
         1167             assert u == self.base_unit
         1168         def cb(amount):
         1169             if amount == '!':
         1170                 screen.is_max = True
         1171                 max_amt = self.get_max_amount()
         1172                 screen.amount = (max_amt + ' ' + self.base_unit) if max_amt else ''
         1173             else:
         1174                 screen.amount = amount
         1175                 screen.is_max = False
         1176         popup = AmountDialog(show_max, amount, cb)
         1177         popup.open()
         1178 
         1179     def addresses_dialog(self):
         1180         from .uix.dialogs.addresses import AddressesDialog
         1181         if self._addresses_dialog is None:
         1182             self._addresses_dialog = AddressesDialog(self)
         1183         self._addresses_dialog.update()
         1184         self._addresses_dialog.open()
         1185 
         1186     def fee_dialog(self):
         1187         from .uix.dialogs.fee_dialog import FeeDialog
         1188         fee_dialog = FeeDialog(self, self.electrum_config, self.set_fee_status)
         1189         fee_dialog.open()
         1190 
         1191     def set_fee_status(self):
         1192         target, tooltip, dyn = self.electrum_config.get_fee_target()
         1193         self.fee_status = target
         1194 
         1195     def on_fee(self, event, *arg):
         1196         self.set_fee_status()
         1197 
         1198     def protected(self, msg, f, args):
         1199         if self.electrum_config.get('pin_code'):
         1200             msg += "\n" + _("Enter your PIN code to proceed")
         1201             on_success = lambda pw: f(*args, self.password)
         1202             d = PincodeDialog(
         1203                 self,
         1204                 message = msg,
         1205                 check_password=self.check_pin_code,
         1206                 on_success=on_success,
         1207                 on_failure=lambda: None)
         1208             d.open()
         1209         else:
         1210             d = Question(
         1211                 msg,
         1212                 lambda b: f(*args, self.password) if b else None,
         1213                 yes_str=_("OK"),
         1214                 no_str=_("Cancel"),
         1215                 title=_("Confirm action"))
         1216             d.open()
         1217 
         1218     def delete_wallet(self):
         1219         basename = os.path.basename(self.wallet.storage.path)
         1220         d = Question(_('Delete wallet?') + '\n' + basename, self._delete_wallet)
         1221         d.open()
         1222 
         1223     def _delete_wallet(self, b):
         1224         if b:
         1225             basename = self.wallet.basename()
         1226             self.protected(_("Are you sure you want to delete wallet {}?").format(basename),
         1227                            self.__delete_wallet, ())
         1228 
         1229     def __delete_wallet(self, pw):
         1230         wallet_path = self.get_wallet_path()
         1231         basename = os.path.basename(wallet_path)
         1232         if self.wallet.has_password():
         1233             try:
         1234                 self.wallet.check_password(pw)
         1235             except InvalidPassword:
         1236                 self.show_error("Invalid password")
         1237                 return
         1238         self.stop_wallet()
         1239         os.unlink(wallet_path)
         1240         self.show_error(_("Wallet removed: {}").format(basename))
         1241         new_path = self.electrum_config.get_wallet_path(use_gui_last_wallet=True)
         1242         self.load_wallet_by_name(new_path)
         1243 
         1244     def show_seed(self, label):
         1245         self.protected(_("Display your seed?"), self._show_seed, (label,))
         1246 
         1247     def _show_seed(self, label, password):
         1248         if self.wallet.has_password() and password is None:
         1249             return
         1250         keystore = self.wallet.keystore
         1251         seed = keystore.get_seed(password)
         1252         passphrase = keystore.get_passphrase(password)
         1253         label.data = seed
         1254         if passphrase:
         1255             label.data += '\n\n' + _('Passphrase') + ': ' + passphrase
         1256 
         1257     def has_pin_code(self):
         1258         return bool(self.electrum_config.get('pin_code'))
         1259 
         1260     def check_pin_code(self, pin):
         1261         if pin != self.electrum_config.get('pin_code'):
         1262             raise InvalidPassword
         1263 
         1264     def change_password(self, cb):
         1265         def on_success(old_password, new_password):
         1266             # called if old_password works on self.wallet
         1267             self.password = new_password
         1268             if self._use_single_password:
         1269                 path = self.wallet.storage.path
         1270                 self.stop_wallet()
         1271                 update_password_for_directory(self.electrum_config, old_password, new_password)
         1272                 self.load_wallet_by_name(path)
         1273                 msg = _("Password updated successfully")
         1274             else:
         1275                 self.wallet.update_password(old_password, new_password)
         1276                 msg = _("Password updated for {}").format(os.path.basename(self.wallet.storage.path))
         1277             self.show_info(msg)
         1278         on_failure = lambda: self.show_error(_("Password not updated"))
         1279         d = ChangePasswordDialog(self, self.wallet, on_success, on_failure)
         1280         d.open()
         1281 
         1282     def change_pin_code(self, cb):
         1283         def on_success(old_password, new_password):
         1284             self.electrum_config.set_key('pin_code', new_password)
         1285             cb()
         1286             self.show_info(_("PIN updated") if new_password else _('PIN disabled'))
         1287         on_failure = lambda: self.show_error(_("PIN not updated"))
         1288         d = PincodeDialog(
         1289             self,
         1290             check_password=self.check_pin_code,
         1291             on_success=on_success,
         1292             on_failure=on_failure,
         1293             is_change=True,
         1294             has_password = self.has_pin_code())
         1295         d.open()
         1296 
         1297     def save_backup(self):
         1298         if platform != 'android':
         1299             self._save_backup()
         1300             return
         1301 
         1302         from android.permissions import request_permissions, Permission
         1303         def cb(permissions, grant_results: Sequence[bool]):
         1304             if not grant_results or not grant_results[0]:
         1305                 self.show_error(_("Cannot save backup without STORAGE permission"))
         1306                 return
         1307             # note: Clock.schedule_once is a hack so that we get called on a non-daemon thread
         1308             #       (needed for WalletDB.write)
         1309             Clock.schedule_once(lambda dt: self._save_backup())
         1310         request_permissions([Permission.WRITE_EXTERNAL_STORAGE], cb)
         1311 
         1312     def _save_backup(self):
         1313         try:
         1314             new_path = self.wallet.save_backup()
         1315         except Exception as e:
         1316             self.logger.exception("Failed to save wallet backup")
         1317             self.show_error("Failed to save wallet backup" + '\n' + str(e))
         1318             return
         1319         if new_path:
         1320             self.show_info(_("Backup saved:") + f"\n{new_path}")
         1321         else:
         1322             self.show_error(_("Backup NOT saved. Backup directory not configured."))
         1323 
         1324     def export_private_keys(self, pk_label, addr):
         1325         if self.wallet.is_watching_only():
         1326             self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))
         1327             return
         1328         def show_private_key(addr, pk_label, password):
         1329             if self.wallet.has_password() and password is None:
         1330                 return
         1331             if not self.wallet.can_export():
         1332                 return
         1333             try:
         1334                 key = str(self.wallet.export_private_key(addr, password))
         1335                 pk_label.data = key
         1336             except InvalidPassword:
         1337                 self.show_error("Invalid PIN")
         1338                 return
         1339         self.protected(_("Decrypt your private key?"), show_private_key, (addr, pk_label))
         1340 
         1341     def import_channel_backup(self, encrypted):
         1342         d = Question(_('Import Channel Backup?'), lambda b: self._import_channel_backup(b, encrypted))
         1343         d.open()
         1344 
         1345     def _import_channel_backup(self, b, encrypted):
         1346         if not b:
         1347             return
         1348         try:
         1349             self.wallet.lnbackups.import_channel_backup(encrypted)
         1350         except Exception as e:
         1351             self.logger.exception("failed to import backup")
         1352             self.show_error("failed to import backup" + '\n' + str(e))
         1353             return
         1354         self.lightning_channels_dialog()