URI: 
       tfile reorganization with top-level module - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 097ac144d976eb46dff809e1809783dc78ab6d8b
   DIR parent 30a7952cbb2e1c59c5eabaaee64d8a4e15a1b0cb
  HTML Author: Janus <ysangkok@gmail.com>
       Date:   Wed, 11 Jul 2018 17:38:47 +0200
       
       file reorganization with top-level module
       
       Diffstat:
         M .gitignore                          |       3 +--
         M README.rst                          |       6 +++---
         M contrib/build-osx/make_osx          |       4 ++--
         M contrib/build-osx/osx.spec          |     193 +++++++++++++++----------------
         M contrib/build-wine/build-electrum-… |       4 ++--
         M contrib/build-wine/deterministic.s… |      41 +++++++++++++++----------------
         M contrib/make_apk                    |       2 +-
         M contrib/make_locale                 |      11 +++++------
         D electrum                            |     480 -------------------------------
         M electrum-env                        |       2 +-
         A electrum/__init__.py                |      14 ++++++++++++++
         A electrum/base_crash_reporter.py     |     128 +++++++++++++++++++++++++++++++
         R lib/base_wizard.py -> electrum/bas… |       0 
         R lib/bitcoin.py -> electrum/bitcoin… |       0 
         R lib/blockchain.py -> electrum/bloc… |       0 
         R lib/checkpoints.json -> electrum/c… |       0 
         R lib/checkpoints_testnet.json -> el… |       0 
         R lib/coinchooser.py -> electrum/coi… |       0 
         A electrum/commands.py                |     892 ++++++++++++++++++++++++++++++
         R lib/constants.py -> electrum/const… |       0 
         R lib/contacts.py -> electrum/contac… |       0 
         R lib/crypto.py -> electrum/crypto.py |       0 
         R lib/currencies.json -> electrum/cu… |       0 
         A electrum/daemon.py                  |     316 +++++++++++++++++++++++++++++++
         R lib/dnssec.py -> electrum/dnssec.py |       0 
         R lib/ecc.py -> electrum/ecc.py       |       0 
         R lib/ecc_fast.py -> electrum/ecc_fa… |       0 
         A electrum/electrum                   |       2 ++
         A electrum/exchange_rate.py           |     573 +++++++++++++++++++++++++++++++
         R gui/__init__.py -> electrum/gui/__… |       0 
         A electrum/gui/kivy/Makefile          |      32 +++++++++++++++++++++++++++++++
         R gui/kivy/Readme.md -> electrum/gui… |       0 
         R gui/kivy/__init__.py -> electrum/g… |       0 
         R gui/kivy/data/background.png -> el… |       0 
         R gui/kivy/data/fonts/Roboto-Bold.tt… |       0 
         R gui/kivy/data/fonts/Roboto-Condens… |       0 
         R gui/kivy/data/fonts/Roboto-Medium.… |       0 
         R gui/kivy/data/fonts/Roboto.ttf -> … |       0 
         R gui/kivy/data/fonts/tron/License.t… |       0 
         R gui/kivy/data/fonts/tron/Readme.tx… |       0 
         R gui/kivy/data/fonts/tron/Tr2n.ttf … |       0 
         R gui/kivy/data/glsl/default.fs -> e… |       0 
         R gui/kivy/data/glsl/default.png -> … |       0 
         R gui/kivy/data/glsl/default.vs -> e… |       0 
         R gui/kivy/data/glsl/header.fs -> el… |       0 
         R gui/kivy/data/glsl/header.vs -> el… |       0 
         R gui/kivy/data/images/defaulttheme-… |       0 
         R gui/kivy/data/images/defaulttheme.… |       0 
         R gui/kivy/data/java-classes/org/ele… |       0 
         R gui/kivy/data/logo/kivy-icon-32.pn… |       0 
         R gui/kivy/data/style.kv -> electrum… |       0 
         R gui/kivy/i18n.py -> electrum/gui/k… |       0 
         A electrum/gui/kivy/main.kv           |     464 ++++++++++++++++++++++++++++++
         A electrum/gui/kivy/main_window.py    |    1028 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/nfc_scanner/__in… |      44 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/nfc_scanner/scan… |     242 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/nfc_scanner/scan… |      52 +++++++++++++++++++++++++++++++
         R gui/kivy/theming/light/action_bar.… |       0 
         R gui/kivy/theming/light/action_butt… |       0 
         R gui/kivy/theming/light/action_grou… |       0 
         R gui/kivy/theming/light/action_grou… |       0 
         R gui/kivy/theming/light/add_contact… |       0 
         R gui/kivy/theming/light/arrow_back.… |       0 
         R gui/kivy/theming/light/bit_logo.pn… |       0 
         R gui/kivy/theming/light/blue_bg_rou… |       0 
         R gui/kivy/theming/light/btn_create_… |       0 
         R gui/kivy/theming/light/btn_create_… |       0 
         R gui/kivy/theming/light/btn_nfc.png… |       0 
         R gui/kivy/theming/light/btn_send_ad… |       0 
         R gui/kivy/theming/light/btn_send_nf… |       0 
         R gui/kivy/theming/light/calculator.… |       0 
         R gui/kivy/theming/light/camera.png … |       0 
         R gui/kivy/theming/light/card.png ->… |       0 
         R gui/kivy/theming/light/card_bottom… |       0 
         R gui/kivy/theming/light/card_btn.pn… |       0 
         R gui/kivy/theming/light/card_top.pn… |       0 
         R gui/kivy/theming/light/carousel_de… |       0 
         R gui/kivy/theming/light/carousel_se… |       0 
         R gui/kivy/theming/light/clock1.png … |       0 
         R gui/kivy/theming/light/clock2.png … |       0 
         R gui/kivy/theming/light/clock3.png … |       0 
         R gui/kivy/theming/light/clock4.png … |       0 
         R gui/kivy/theming/light/clock5.png … |       0 
         R gui/kivy/theming/light/close.png -… |       0 
         R gui/kivy/theming/light/closebutton… |       0 
         R gui/kivy/theming/light/confirmed.p… |       0 
         R gui/kivy/theming/light/contact.png… |       0 
         R gui/kivy/theming/light/contact_ove… |       0 
         R gui/kivy/theming/light/create_act_… |       0 
         R gui/kivy/theming/light/create_act_… |       0 
         R gui/kivy/theming/light/dialog.png … |       0 
         R gui/kivy/theming/light/dropdown_ba… |       0 
         R gui/kivy/theming/light/electrum_ic… |       0 
         R gui/kivy/theming/light/error.png -… |       0 
         R gui/kivy/theming/light/gear.png ->… |       0 
         R gui/kivy/theming/light/globe.png -… |       0 
         R gui/kivy/theming/light/icon_border… |       0 
         R gui/kivy/theming/light/important.p… |       0 
         R gui/kivy/theming/light/info.png ->… |       0 
         R gui/kivy/theming/light/lightblue_b… |       0 
         R gui/kivy/theming/light/logo.png ->… |       0 
         R gui/kivy/theming/light/logo_atom_d… |       0 
         R gui/kivy/theming/light/mail_icon.p… |       0 
         R gui/kivy/theming/light/manualentry… |       0 
         R gui/kivy/theming/light/network.png… |       0 
         R gui/kivy/theming/light/nfc.png -> … |       0 
         R gui/kivy/theming/light/nfc_clock.p… |       0 
         R gui/kivy/theming/light/nfc_phone.p… |       0 
         R gui/kivy/theming/light/nfc_stage_o… |       0 
         R gui/kivy/theming/light/overflow_ba… |       0 
         R gui/kivy/theming/light/overflow_bt… |       0 
         R gui/kivy/theming/light/paste_icon.… |       0 
         R gui/kivy/theming/light/pen.png -> … |       0 
         R gui/kivy/theming/light/qrcode.png … |       0 
         R gui/kivy/theming/light/save.png ->… |       0 
         R gui/kivy/theming/light/settings.pn… |       0 
         R gui/kivy/theming/light/shadow.png … |       0 
         R gui/kivy/theming/light/shadow_righ… |       0 
         R gui/kivy/theming/light/share.png -… |       0 
         R gui/kivy/theming/light/star_big_in… |       0 
         R gui/kivy/theming/light/stepper_ful… |       0 
         R gui/kivy/theming/light/stepper_lef… |       0 
         R gui/kivy/theming/light/stepper_res… |       0 
         R gui/kivy/theming/light/stepper_res… |       0 
         R gui/kivy/theming/light/tab.png -> … |       0 
         R gui/kivy/theming/light/tab_btn.png… |       0 
         R gui/kivy/theming/light/tab_btn_dis… |       0 
         R gui/kivy/theming/light/tab_btn_pre… |       0 
         R gui/kivy/theming/light/tab_disable… |       0 
         R gui/kivy/theming/light/tab_strip.p… |       0 
         R gui/kivy/theming/light/textinput_a… |       0 
         R gui/kivy/theming/light/unconfirmed… |       0 
         R gui/kivy/theming/light/wallet.png … |       0 
         R gui/kivy/theming/light/wallets.png… |       0 
         R gui/kivy/theming/light/white_bg_ro… |       0 
         R gui/kivy/tools/bitcoin_intent.xml … |       0 
         R gui/kivy/tools/blacklist.txt -> el… |       0 
         R gui/kivy/tools/buildozer.spec -> e… |       0 
         R gui/kivy/uix/__init__.py -> electr… |       0 
         R gui/kivy/uix/combobox.py -> electr… |       0 
         A electrum/gui/kivy/uix/context_menu… |      56 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/__in… |     220 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/addr… |     180 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/dialogs/amount_dialog… |       0 
         A electrum/gui/kivy/uix/dialogs/bump… |     118 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/dialogs/checkbox_dial… |       0 
         R gui/kivy/uix/dialogs/choice_dialog… |       0 
         R gui/kivy/uix/dialogs/crash_reporte… |       0 
         A electrum/gui/kivy/uix/dialogs/fee_… |     131 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/fx_d… |     111 ++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/inst… |    1038 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/invo… |     169 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/labe… |      55 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/nfc_… |      33 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/pass… |     142 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/dialogs/qr_dialog.py … |       0 
         A electrum/gui/kivy/uix/dialogs/qr_s… |      44 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/ques… |      53 ++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/requ… |     157 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/dialogs/seed_options.… |       0 
         A electrum/gui/kivy/uix/dialogs/sett… |     220 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/tx_d… |     184 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/dialogs/wallets.py ->… |       0 
         R gui/kivy/uix/drawer.py -> electrum… |       0 
         R gui/kivy/uix/gridview.py -> electr… |       0 
         A electrum/gui/kivy/uix/menus.py      |      95 ++++++++++++++++++++++++++++++
         R gui/kivy/uix/qrcodewidget.py -> el… |       0 
         A electrum/gui/kivy/uix/screens.py    |     484 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/ui_screens/about.kv -… |       0 
         A electrum/gui/kivy/uix/ui_screens/h… |      78 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/ui_screens/invoice.kv… |       0 
         R gui/kivy/uix/ui_screens/network.kv… |       0 
         R gui/kivy/uix/ui_screens/proxy.kv -… |       0 
         A electrum/gui/kivy/uix/ui_screens/r… |     142 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/ui_screens/s… |     127 +++++++++++++++++++++++++++++++
         R gui/kivy/uix/ui_screens/server.kv … |       0 
         R gui/kivy/uix/ui_screens/status.kv … |       0 
         A electrum/gui/qt/__init__.py         |     313 +++++++++++++++++++++++++++++++
         R gui/qt/address_dialog.py -> electr… |       0 
         A electrum/gui/qt/address_list.py     |     195 +++++++++++++++++++++++++++++++
         R gui/qt/amountedit.py -> electrum/g… |       0 
         A electrum/gui/qt/completion_text_ed… |     120 +++++++++++++++++++++++++++++++
         R gui/qt/console.py -> electrum/gui/… |       0 
         A electrum/gui/qt/contact_list.py     |      98 +++++++++++++++++++++++++++++++
         R gui/qt/exception_window.py -> elec… |       0 
         R gui/qt/fee_slider.py -> electrum/g… |       0 
         R gui/qt/history_list.py -> electrum… |       0 
         A electrum/gui/qt/installwizard.py    |     644 +++++++++++++++++++++++++++++++
         R gui/qt/invoice_list.py -> electrum… |       0 
         A electrum/gui/qt/main_window.py      |    3220 +++++++++++++++++++++++++++++++
         R gui/qt/network_dialog.py -> electr… |       0 
         A electrum/gui/qt/password_dialog.py  |     305 +++++++++++++++++++++++++++++++
         R gui/qt/paytoedit.py -> electrum/gu… |       0 
         R gui/qt/qrcodewidget.py -> electrum… |       0 
         A electrum/gui/qt/qrtextedit.py       |      76 +++++++++++++++++++++++++++++++
         A electrum/gui/qt/qrwindow.py         |      89 +++++++++++++++++++++++++++++++
         A electrum/gui/qt/request_list.py     |     129 +++++++++++++++++++++++++++++++
         A electrum/gui/qt/seed_dialog.py      |     211 +++++++++++++++++++++++++++++++
         A electrum/gui/qt/transaction_dialog… |     328 +++++++++++++++++++++++++++++++
         R gui/qt/util.py -> electrum/gui/qt/… |       0 
         R gui/qt/utxo_list.py -> electrum/gu… |       0 
         R gui/stdio.py -> electrum/gui/stdio… |       0 
         A electrum/gui/text.py                |     503 +++++++++++++++++++++++++++++++
         A electrum/i18n.py                    |      81 ++++++++++++++++++++++++++++++
         A electrum/interface.py               |     407 +++++++++++++++++++++++++++++++
         A electrum/jsonrpc.py                 |      98 +++++++++++++++++++++++++++++++
         A electrum/keystore.py                |     798 ++++++++++++++++++++++++++++++
         A electrum/mnemonic.py                |     183 +++++++++++++++++++++++++++++++
         A electrum/msqr.py                    |      94 +++++++++++++++++++++++++++++++
         A electrum/network.py                 |    1297 +++++++++++++++++++++++++++++++
         A electrum/old_mnemonic.py            |    1697 +++++++++++++++++++++++++++++++
         A electrum/paymentrequest.proto       |      47 +++++++++++++++++++++++++++++++
         A electrum/paymentrequest.py          |     523 +++++++++++++++++++++++++++++++
         A electrum/paymentrequest_pb2.py      |     367 ++++++++++++++++++++++++++++++
         A electrum/pem.py                     |     191 +++++++++++++++++++++++++++++++
         A electrum/plot.py                    |      63 +++++++++++++++++++++++++++++++
         A electrum/plugin.py                  |     566 +++++++++++++++++++++++++++++++
         A electrum/plugins/README             |      31 +++++++++++++++++++++++++++++++
         A electrum/plugins/__init__.py        |      26 ++++++++++++++++++++++++++
         A electrum/plugins/audio_modem/__ini… |       7 +++++++
         A electrum/plugins/audio_modem/qt.py  |     128 +++++++++++++++++++++++++++++++
         A electrum/plugins/cosigner_pool/__i… |       9 +++++++++
         A electrum/plugins/cosigner_pool/qt.… |     228 +++++++++++++++++++++++++++++++
         A electrum/plugins/digitalbitbox/__i… |       6 ++++++
         A electrum/plugins/digitalbitbox/cmd… |      14 ++++++++++++++
         A electrum/plugins/digitalbitbox/dig… |     767 +++++++++++++++++++++++++++++++
         A electrum/plugins/digitalbitbox/qt.… |      43 ++++++++++++++++++++++++++++++
         A electrum/plugins/email_requests/__… |       5 +++++
         A electrum/plugins/email_requests/qt… |     271 +++++++++++++++++++++++++++++++
         A electrum/plugins/greenaddress_inst… |       5 +++++
         A electrum/plugins/greenaddress_inst… |     107 +++++++++++++++++++++++++++++++
         A electrum/plugins/hw_wallet/__init_… |       2 ++
         A electrum/plugins/hw_wallet/cmdline… |      46 +++++++++++++++++++++++++++++++
         A electrum/plugins/hw_wallet/plugin.… |      89 +++++++++++++++++++++++++++++++
         A electrum/plugins/hw_wallet/qt.py    |     235 +++++++++++++++++++++++++++++++
         A electrum/plugins/keepkey/__init__.… |       7 +++++++
         A electrum/plugins/keepkey/client.py  |      14 ++++++++++++++
         A electrum/plugins/keepkey/clientbas… |     250 +++++++++++++++++++++++++++++++
         A electrum/plugins/keepkey/cmdline.py |      14 ++++++++++++++
         A electrum/plugins/keepkey/keepkey.py |     438 +++++++++++++++++++++++++++++++
         A electrum/plugins/keepkey/qt.py      |     586 ++++++++++++++++++++++++++++++
         A electrum/plugins/labels/__init__.py |       9 +++++++++
         A electrum/plugins/labels/cmdline.py  |      11 +++++++++++
         A electrum/plugins/labels/kivy.py     |      14 ++++++++++++++
         A electrum/plugins/labels/labels.py   |     167 +++++++++++++++++++++++++++++++
         A electrum/plugins/labels/qt.py       |      78 +++++++++++++++++++++++++++++++
         A electrum/plugins/ledger/__init__.py |       7 +++++++
         A electrum/plugins/ledger/auth2fa.py  |     358 +++++++++++++++++++++++++++++++
         A electrum/plugins/ledger/cmdline.py  |      14 ++++++++++++++
         A electrum/plugins/ledger/ledger.py   |     637 +++++++++++++++++++++++++++++++
         A electrum/plugins/ledger/qt.py       |      81 ++++++++++++++++++++++++++++++
         A electrum/plugins/revealer/DejaVuSa… |       0 
         A electrum/plugins/revealer/LICENSE_… |      99 +++++++++++++++++++++++++++++++
         A electrum/plugins/revealer/SIL Open… |      44 +++++++++++++++++++++++++++++++
         A electrum/plugins/revealer/SourceSa… |       0 
         A electrum/plugins/revealer/__init__… |      16 ++++++++++++++++
         A electrum/plugins/revealer/qt.py     |     724 +++++++++++++++++++++++++++++++
         A electrum/plugins/trezor/__init__.py |       8 ++++++++
         A electrum/plugins/trezor/client.py   |      11 +++++++++++
         A electrum/plugins/trezor/clientbase… |     265 +++++++++++++++++++++++++++++++
         A electrum/plugins/trezor/cmdline.py  |      14 ++++++++++++++
         A electrum/plugins/trezor/qt.py       |     613 +++++++++++++++++++++++++++++++
         A electrum/plugins/trezor/transport.… |      95 ++++++++++++++++++++++++++++++
         A electrum/plugins/trezor/trezor.py   |     516 +++++++++++++++++++++++++++++++
         A electrum/plugins/trustedcoin/__ini… |      11 +++++++++++
         A electrum/plugins/trustedcoin/cmdli… |      45 +++++++++++++++++++++++++++++++
         A electrum/plugins/trustedcoin/kivy.… |     110 +++++++++++++++++++++++++++++++
         A electrum/plugins/trustedcoin/qt.py  |     313 +++++++++++++++++++++++++++++++
         A electrum/plugins/trustedcoin/trust… |     672 +++++++++++++++++++++++++++++++
         A electrum/plugins/virtualkeyboard/_… |       5 +++++
         A electrum/plugins/virtualkeyboard/q… |      61 +++++++++++++++++++++++++++++++
         A electrum/qrscanner.py               |      88 +++++++++++++++++++++++++++++++
         A electrum/ripemd.py                  |     393 +++++++++++++++++++++++++++++++
         A electrum/rsakey.py                  |     542 +++++++++++++++++++++++++++++++
         A electrum/scripts/bip70.py           |      35 +++++++++++++++++++++++++++++++
         A electrum/scripts/block_headers.py   |      29 +++++++++++++++++++++++++++++
         A electrum/scripts/estimate_fee.py    |       7 +++++++
         A electrum/scripts/get_history.py     |      18 ++++++++++++++++++
         A electrum/scripts/peers.py           |      14 ++++++++++++++
         A electrum/scripts/servers.py         |      10 ++++++++++
         A electrum/scripts/txradar.py         |      20 ++++++++++++++++++++
         A electrum/scripts/util.py            |      87 +++++++++++++++++++++++++++++++
         A electrum/scripts/watch_address.py   |      36 +++++++++++++++++++++++++++++++
         A electrum/segwit_addr.py             |     122 +++++++++++++++++++++++++++++++
         A electrum/servers.json               |     304 +++++++++++++++++++++++++++++++
         A electrum/servers_regtest.json       |       8 ++++++++
         A electrum/servers_testnet.json       |      31 +++++++++++++++++++++++++++++++
         A electrum/simple_config.py           |     552 ++++++++++++++++++++++++++++++
         A electrum/storage.py                 |     645 +++++++++++++++++++++++++++++++
         A electrum/synchronizer.py            |     213 +++++++++++++++++++++++++++++++
         A electrum/tests/__init__.py          |      38 +++++++++++++++++++++++++++++++
         A electrum/tests/test_bitcoin.py      |     761 ++++++++++++++++++++++++++++++
         A electrum/tests/test_commands.py     |      33 +++++++++++++++++++++++++++++++
         A electrum/tests/test_dnssec.py       |      41 +++++++++++++++++++++++++++++++
         A electrum/tests/test_interface.py    |      28 ++++++++++++++++++++++++++++
         A electrum/tests/test_mnemonic.py     |      42 +++++++++++++++++++++++++++++++
         A electrum/tests/test_simple_config.… |     149 +++++++++++++++++++++++++++++++
         A electrum/tests/test_storage_upgrad… |     301 +++++++++++++++++++++++++++++++
         A electrum/tests/test_transaction.py  |     813 ++++++++++++++++++++++++++++++
         A electrum/tests/test_util.py         |     109 +++++++++++++++++++++++++++++++
         A electrum/tests/test_wallet.py       |      71 +++++++++++++++++++++++++++++++
         A electrum/tests/test_wallet_vertica… |    1603 +++++++++++++++++++++++++++++++
         A electrum/transaction.py             |    1229 +++++++++++++++++++++++++++++++
         A electrum/util.py                    |     903 ++++++++++++++++++++++++++++++
         A electrum/verifier.py                |     158 +++++++++++++++++++++++++++++++
         A electrum/version.py                 |      18 ++++++++++++++++++
         A electrum/wallet.py                  |    2374 +++++++++++++++++++++++++++++++
         A electrum/websockets.py              |     140 +++++++++++++++++++++++++++++++
         A electrum/wordlist/chinese_simplifi… |    2048 +++++++++++++++++++++++++++++++
         A electrum/wordlist/english.txt       |    2048 +++++++++++++++++++++++++++++++
         A electrum/wordlist/japanese.txt      |    2048 +++++++++++++++++++++++++++++++
         A electrum/wordlist/portuguese.txt    |    1654 +++++++++++++++++++++++++++++++
         A electrum/wordlist/spanish.txt       |    2048 +++++++++++++++++++++++++++++++
         A electrum/x509.py                    |     341 +++++++++++++++++++++++++++++++
         D gui/kivy/Makefile                   |      32 -------------------------------
         D gui/kivy/main.kv                    |     464 ------------------------------
         D gui/kivy/main_window.py             |    1028 -------------------------------
         D gui/kivy/nfc_scanner/__init__.py    |      44 -------------------------------
         D gui/kivy/nfc_scanner/scanner_andro… |     242 -------------------------------
         D gui/kivy/nfc_scanner/scanner_dummy… |      52 -------------------------------
         D gui/kivy/uix/context_menu.py        |      56 -------------------------------
         D gui/kivy/uix/dialogs/__init__.py    |     220 -------------------------------
         D gui/kivy/uix/dialogs/addresses.py   |     180 -------------------------------
         D gui/kivy/uix/dialogs/bump_fee_dial… |     118 -------------------------------
         D gui/kivy/uix/dialogs/fee_dialog.py  |     131 -------------------------------
         D gui/kivy/uix/dialogs/fx_dialog.py   |     111 ------------------------------
         D gui/kivy/uix/dialogs/installwizard… |    1038 -------------------------------
         D gui/kivy/uix/dialogs/invoices.py    |     169 -------------------------------
         D gui/kivy/uix/dialogs/label_dialog.… |      55 -------------------------------
         D gui/kivy/uix/dialogs/nfc_transacti… |      33 -------------------------------
         D gui/kivy/uix/dialogs/password_dial… |     142 -------------------------------
         D gui/kivy/uix/dialogs/qr_scanner.py  |      44 -------------------------------
         D gui/kivy/uix/dialogs/question.py    |      53 ------------------------------
         D gui/kivy/uix/dialogs/requests.py    |     157 -------------------------------
         D gui/kivy/uix/dialogs/settings.py    |     220 -------------------------------
         D gui/kivy/uix/dialogs/tx_dialog.py   |     184 -------------------------------
         D gui/kivy/uix/menus.py               |      95 ------------------------------
         D gui/kivy/uix/screens.py             |     484 -------------------------------
         D gui/kivy/uix/ui_screens/history.kv  |      78 -------------------------------
         D gui/kivy/uix/ui_screens/receive.kv  |     142 -------------------------------
         D gui/kivy/uix/ui_screens/send.kv     |     127 -------------------------------
         D gui/qt/__init__.py                  |     313 -------------------------------
         D gui/qt/address_list.py              |     195 -------------------------------
         D gui/qt/completion_text_edit.py      |     121 -------------------------------
         D gui/qt/contact_list.py              |      98 -------------------------------
         D gui/qt/installwizard.py             |     643 -------------------------------
         D gui/qt/main_window.py               |    3221 -------------------------------
         D gui/qt/password_dialog.py           |     305 -------------------------------
         D gui/qt/qrtextedit.py                |      76 -------------------------------
         D gui/qt/qrwindow.py                  |      89 -------------------------------
         D gui/qt/request_list.py              |     129 -------------------------------
         D gui/qt/seed_dialog.py               |     211 -------------------------------
         D gui/qt/transaction_dialog.py        |     328 -------------------------------
         D gui/text.py                         |     503 -------------------------------
         D lib/__init__.py                     |      14 --------------
         D lib/base_crash_reporter.py          |     127 -------------------------------
         D lib/commands.py                     |     892 ------------------------------
         D lib/daemon.py                       |     316 -------------------------------
         D lib/exchange_rate.py                |     573 -------------------------------
         D lib/i18n.py                         |      81 ------------------------------
         D lib/interface.py                    |     407 -------------------------------
         D lib/jsonrpc.py                      |      98 -------------------------------
         D lib/keystore.py                     |     799 -------------------------------
         D lib/mnemonic.py                     |     183 -------------------------------
         D lib/msqr.py                         |      94 -------------------------------
         D lib/network.py                      |    1297 -------------------------------
         D lib/old_mnemonic.py                 |    1697 -------------------------------
         D lib/paymentrequest.proto            |      47 -------------------------------
         D lib/paymentrequest.py               |     528 -------------------------------
         D lib/paymentrequest_pb2.py           |     367 ------------------------------
         D lib/pem.py                          |     191 -------------------------------
         D lib/plot.py                         |      63 -------------------------------
         D lib/plugins.py                      |     572 -------------------------------
         D lib/qrscanner.py                    |      88 -------------------------------
         D lib/ripemd.py                       |     393 -------------------------------
         D lib/rsakey.py                       |     542 -------------------------------
         D lib/segwit_addr.py                  |     122 -------------------------------
         D lib/servers.json                    |     304 -------------------------------
         D lib/servers_regtest.json            |       8 --------
         D lib/servers_testnet.json            |      31 -------------------------------
         D lib/simple_config.py                |     552 ------------------------------
         D lib/storage.py                      |     647 -------------------------------
         D lib/synchronizer.py                 |     213 -------------------------------
         D lib/tests/__init__.py               |      38 -------------------------------
         D lib/tests/test_bitcoin.py           |     761 ------------------------------
         D lib/tests/test_commands.py          |      33 -------------------------------
         D lib/tests/test_dnssec.py            |      41 -------------------------------
         D lib/tests/test_interface.py         |      28 ----------------------------
         D lib/tests/test_mnemonic.py          |      42 -------------------------------
         D lib/tests/test_simple_config.py     |     149 -------------------------------
         D lib/tests/test_storage_upgrade.py   |     301 -------------------------------
         D lib/tests/test_transaction.py       |     813 ------------------------------
         D lib/tests/test_util.py              |     109 -------------------------------
         D lib/tests/test_wallet.py            |      71 -------------------------------
         D lib/tests/test_wallet_vertical.py   |    1604 -------------------------------
         D lib/transaction.py                  |    1229 -------------------------------
         D lib/util.py                         |     903 ------------------------------
         D lib/verifier.py                     |     158 -------------------------------
         D lib/version.py                      |      18 ------------------
         D lib/wallet.py                       |    2377 -------------------------------
         D lib/websockets.py                   |     140 -------------------------------
         D lib/wordlist/chinese_simplified.txt |    2048 -------------------------------
         D lib/wordlist/english.txt            |    2048 -------------------------------
         D lib/wordlist/japanese.txt           |    2048 -------------------------------
         D lib/wordlist/portuguese.txt         |    1654 -------------------------------
         D lib/wordlist/spanish.txt            |    2048 -------------------------------
         D lib/x509.py                         |     341 -------------------------------
         D plugins/README                      |      31 -------------------------------
         D plugins/__init__.py                 |      26 --------------------------
         D plugins/audio_modem/__init__.py     |       7 -------
         D plugins/audio_modem/qt.py           |     128 -------------------------------
         D plugins/cosigner_pool/__init__.py   |       9 ---------
         D plugins/cosigner_pool/qt.py         |     228 -------------------------------
         D plugins/digitalbitbox/__init__.py   |       6 ------
         D plugins/digitalbitbox/cmdline.py    |      14 --------------
         D plugins/digitalbitbox/digitalbitbo… |     768 -------------------------------
         D plugins/digitalbitbox/qt.py         |      43 ------------------------------
         D plugins/email_requests/__init__.py  |       5 -----
         D plugins/email_requests/qt.py        |     271 -------------------------------
         D plugins/greenaddress_instant/__ini… |       5 -----
         D plugins/greenaddress_instant/qt.py  |     107 -------------------------------
         D plugins/hw_wallet/__init__.py       |       2 --
         D plugins/hw_wallet/cmdline.py        |      46 -------------------------------
         D plugins/hw_wallet/plugin.py         |      89 -------------------------------
         D plugins/hw_wallet/qt.py             |     235 -------------------------------
         D plugins/keepkey/__init__.py         |       7 -------
         D plugins/keepkey/client.py           |      14 --------------
         D plugins/keepkey/clientbase.py       |     250 -------------------------------
         D plugins/keepkey/cmdline.py          |      14 --------------
         D plugins/keepkey/keepkey.py          |     438 -------------------------------
         D plugins/keepkey/qt.py               |     586 ------------------------------
         D plugins/labels/__init__.py          |       9 ---------
         D plugins/labels/cmdline.py           |      11 -----------
         D plugins/labels/kivy.py              |      14 --------------
         D plugins/labels/labels.py            |     168 -------------------------------
         D plugins/labels/qt.py                |      78 -------------------------------
         D plugins/ledger/__init__.py          |       7 -------
         D plugins/ledger/auth2fa.py           |     358 -------------------------------
         D plugins/ledger/cmdline.py           |      14 --------------
         D plugins/ledger/ledger.py            |     637 -------------------------------
         D plugins/ledger/qt.py                |      81 ------------------------------
         D plugins/revealer/DejaVuSansMono-Bo… |       0 
         D plugins/revealer/LICENSE_DEJAVU.txt |      99 -------------------------------
         D plugins/revealer/SIL Open Font Lic… |      44 -------------------------------
         D plugins/revealer/SourceSansPro-Bol… |       0 
         D plugins/revealer/__init__.py        |      17 -----------------
         D plugins/revealer/qt.py              |     723 -------------------------------
         D plugins/trezor/__init__.py          |       8 --------
         D plugins/trezor/client.py            |      11 -----------
         D plugins/trezor/clientbase.py        |     265 -------------------------------
         D plugins/trezor/cmdline.py           |      14 --------------
         D plugins/trezor/qt.py                |     613 -------------------------------
         D plugins/trezor/transport.py         |      95 ------------------------------
         D plugins/trezor/trezor.py            |     516 -------------------------------
         D plugins/trustedcoin/__init__.py     |      11 -----------
         D plugins/trustedcoin/cmdline.py      |      45 -------------------------------
         D plugins/trustedcoin/kivy.py         |     110 -------------------------------
         D plugins/trustedcoin/qt.py           |     313 -------------------------------
         D plugins/trustedcoin/trustedcoin.py  |     676 -------------------------------
         D plugins/virtualkeyboard/__init__.py |       5 -----
         D plugins/virtualkeyboard/qt.py       |      61 -------------------------------
         A run_electrum                        |     473 ++++++++++++++++++++++++++++++
         D scripts/bip70                       |      35 -------------------------------
         D scripts/block_headers               |      29 -----------------------------
         D scripts/estimate_fee                |       6 ------
         D scripts/get_history                 |      18 ------------------
         D scripts/peers                       |      14 --------------
         D scripts/servers                     |       9 ---------
         D scripts/txradar                     |      19 -------------------
         D scripts/util.py                     |      87 -------------------------------
         D scripts/watch_address               |      36 -------------------------------
         M setup.py                            |      44 +++++++++++++++----------------
         M snap/snapcraft.yaml                 |       2 +-
         M tox.ini                             |       2 +-
       
       474 files changed, 51372 insertions(+), 51404 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -4,10 +4,9 @@
        build/
        dist/
        *.egg/
       -/electrum.py
        contrib/pyinstaller/
        Electrum.egg-info/
       -gui/qt/icons_rc.py
       +electrum/gui/qt/icons_rc.py
        locale/
        .devlocaltmp/
        *_trial_temp
   DIR diff --git a/README.rst b/README.rst
       t@@ -36,7 +36,7 @@ Electrum from its root directory, without installing it on your
        system; all the python dependencies are included in the 'packages'
        directory. To run Electrum from its root directory, just do::
        
       -    ./electrum
       +    ./run_electrum
        
        You can also install Electrum on your system, by running this command::
        
       t@@ -73,12 +73,12 @@ Render the SVG icons to PNGs (optional)::
        Compile the icons file for Qt::
        
            sudo apt-get install pyqt5-dev-tools
       -    pyrcc5 icons.qrc -o gui/qt/icons_rc.py
       +    pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py
        
        Compile the protobuf description file::
        
            sudo apt-get install protobuf-compiler
       -    protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto
       +    protoc --proto_path=electrum --python_out=electrum electrum/paymentrequest.proto
        
        Create translations (optional)::
        
   DIR diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx
       t@@ -46,8 +46,8 @@ git submodule update
        rm  -rf $BUILDDIR > /dev/null 2>&1
        mkdir $BUILDDIR
        
       -cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./lib/locale/
       -cp    ./contrib/deterministic-build/electrum-icons/icons_rc.py ./gui/qt/
       +cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./electrum/locale/
       +cp    ./contrib/deterministic-build/electrum-icons/icons_rc.py ./electrum/gui/qt/
        
        
        info "Downloading libusb..."
   DIR diff --git a/contrib/build-osx/osx.spec b/contrib/build-osx/osx.spec
       t@@ -1,97 +1,96 @@
       -# -*- mode: python -*-
       -
       -from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
       -
       -import sys
       -import os
       -
       -PACKAGE='Electrum'
       -PYPKG='electrum'
       -MAIN_SCRIPT='electrum'
       -ICONS_FILE='electrum.icns'
       -
       -for i, x in enumerate(sys.argv):
       -    if x == '--name':
       -        VERSION = sys.argv[i+1]
       -        break
       -else:
       -    raise Exception('no version')
       -
       -electrum = os.path.abspath(".") + "/"
       -block_cipher = None
       -
       -# see https://github.com/pyinstaller/pyinstaller/issues/2005
       -hiddenimports = []
       -hiddenimports += collect_submodules('trezorlib')
       -hiddenimports += collect_submodules('btchip')
       -hiddenimports += collect_submodules('keepkeylib')
       -hiddenimports += collect_submodules('websocket')
       -
       -datas = [
       -    (electrum+'lib/*.json', PYPKG),
       -    (electrum+'lib/wordlist/english.txt', PYPKG + '/wordlist'),
       -    (electrum+'lib/locale', PYPKG + '/locale'),
       -    (electrum+'plugins', PYPKG + '_plugins'),
       -]
       -datas += collect_data_files('trezorlib')
       -datas += collect_data_files('btchip')
       -datas += collect_data_files('keepkeylib')
       -
       -# Add libusb so Trezor will work
       -binaries = [(electrum + "contrib/build-osx/libusb-1.0.dylib", ".")]
       -binaries += [(electrum + "contrib/build-osx/libsecp256k1.0.dylib", ".")]
       -
       -# Workaround for "Retro Look":
       -binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
       -
       -# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
       -a = Analysis([electrum+MAIN_SCRIPT,
       -              electrum+'gui/qt/main_window.py',
       -              electrum+'gui/text.py',
       -              electrum+'lib/util.py',
       -              electrum+'lib/wallet.py',
       -              electrum+'lib/simple_config.py',
       -              electrum+'lib/bitcoin.py',
       -              electrum+'lib/dnssec.py',
       -              electrum+'lib/commands.py',
       -              electrum+'plugins/cosigner_pool/qt.py',
       -              electrum+'plugins/email_requests/qt.py',
       -              electrum+'plugins/trezor/client.py',
       -              electrum+'plugins/trezor/qt.py',
       -              electrum+'plugins/keepkey/qt.py',
       -              electrum+'plugins/ledger/qt.py',
       -              ],
       -             binaries=binaries,
       -             datas=datas,
       -             hiddenimports=hiddenimports,
       -             hookspath=[])
       -
       -# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
       -for d in a.datas:
       -    if 'pyconfig' in d[0]:
       -        a.datas.remove(d)
       -        break
       -
       -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
       -
       -exe = EXE(pyz,
       -          a.scripts,
       -          a.binaries,
       -          a.datas,
       -          name=PACKAGE,
       -          debug=False,
       -          strip=False,
       -          upx=True,
       -          icon=electrum+ICONS_FILE,
       -          console=False)
       -
       -app = BUNDLE(exe,
       -             version = VERSION,
       -             name=PACKAGE + '.app',
       -             icon=electrum+ICONS_FILE,
       -             bundle_identifier=None,
       -             info_plist={
       -                'NSHighResolutionCapable': 'True',
       -                'NSSupportsAutomaticGraphicsSwitching': 'True'
       -             }
       -)
       +# -*- mode: python -*-
       +
       +from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
       +
       +import sys
       +import os
       +
       +PACKAGE='Electrum'
       +PYPKG='electrum'
       +MAIN_SCRIPT='run_electrum'
       +ICONS_FILE='electrum.icns'
       +
       +for i, x in enumerate(sys.argv):
       +    if x == '--name':
       +        VERSION = sys.argv[i+1]
       +        break
       +else:
       +    raise Exception('no version')
       +
       +electrum = os.path.abspath(".") + "/"
       +block_cipher = None
       +
       +# see https://github.com/pyinstaller/pyinstaller/issues/2005
       +hiddenimports = []
       +hiddenimports += collect_submodules('trezorlib')
       +hiddenimports += collect_submodules('btchip')
       +hiddenimports += collect_submodules('keepkeylib')
       +hiddenimports += collect_submodules('websocket')
       +
       +datas = [
       +    (electrum+'electrum/*.json', PYPKG),
       +    (electrum+'electrum/wordlist/english.txt', PYPKG + '/wordlist'),
       +    (electrum+'electrum/locale', PYPKG + '/locale')
       +]
       +datas += collect_data_files('trezorlib')
       +datas += collect_data_files('btchip')
       +datas += collect_data_files('keepkeylib')
       +
       +# Add libusb so Trezor will work
       +binaries = [(electrum + "contrib/build-osx/libusb-1.0.dylib", ".")]
       +binaries += [(electrum + "contrib/build-osx/libsecp256k1.0.dylib", ".")]
       +
       +# Workaround for "Retro Look":
       +binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
       +
       +# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
       +a = Analysis([electrum+ MAIN_SCRIPT,
       +              electrum+'electrum/gui/qt/main_window.py',
       +              electrum+'electrum/gui/text.py',
       +              electrum+'electrum/util.py',
       +              electrum+'electrum/wallet.py',
       +              electrum+'electrum/simple_config.py',
       +              electrum+'electrum/bitcoin.py',
       +              electrum+'electrum/dnssec.py',
       +              electrum+'electrum/commands.py',
       +              electrum+'electrum/plugins/cosigner_pool/qt.py',
       +              electrum+'electrum/plugins/email_requests/qt.py',
       +              electrum+'electrum/plugins/trezor/client.py',
       +              electrum+'electrum/plugins/trezor/qt.py',
       +              electrum+'electrum/plugins/keepkey/qt.py',
       +              electrum+'electrum/plugins/ledger/qt.py',
       +              ],
       +             binaries=binaries,
       +             datas=datas,
       +             hiddenimports=hiddenimports,
       +             hookspath=[])
       +
       +# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
       +for d in a.datas:
       +    if 'pyconfig' in d[0]:
       +        a.datas.remove(d)
       +        break
       +
       +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
       +
       +exe = EXE(pyz,
       +          a.scripts,
       +          a.binaries,
       +          a.datas,
       +          name=PACKAGE,
       +          debug=False,
       +          strip=False,
       +          upx=True,
       +          icon=electrum+ICONS_FILE,
       +          console=False)
       +
       +app = BUNDLE(exe,
       +             version = VERSION,
       +             name=PACKAGE + '.app',
       +             icon=electrum+ICONS_FILE,
       +             bundle_identifier=None,
       +             info_plist={
       +                'NSHighResolutionCapable': 'True',
       +                'NSSupportsAutomaticGraphicsSwitching': 'True'
       +             }
       +)
   DIR diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh
       t@@ -62,8 +62,8 @@ popd
        rm -rf $WINEPREFIX/drive_c/electrum
        cp -r electrum $WINEPREFIX/drive_c/electrum
        cp electrum/LICENCE .
       -cp -r ./electrum/contrib/deterministic-build/electrum-locale/locale $WINEPREFIX/drive_c/electrum/lib/
       -cp ./electrum/contrib/deterministic-build/electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/gui/qt/
       +cp -r ./electrum/contrib/deterministic-build/electrum-locale/locale $WINEPREFIX/drive_c/electrum/electrum/
       +cp ./electrum/contrib/deterministic-build/electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/electrum/gui/qt/
        
        # Install frozen dependencies
        $PYTHON -m pip install -r ../../deterministic-build/requirements.txt
   DIR diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec
       t@@ -31,10 +31,9 @@ binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]
        binaries += [('C:/tmp/libsecp256k1.dll', '.')]
        
        datas = [
       -    (home+'lib/*.json', 'electrum'),
       -    (home+'lib/wordlist/english.txt', 'electrum/wordlist'),
       -    (home+'lib/locale', 'electrum/locale'),
       -    (home+'plugins', 'electrum_plugins'),
       +    (home+'electrum/*.json', 'electrum'),
       +    (home+'electrum/wordlist/english.txt', 'electrum/wordlist'),
       +    (home+'electrum/locale', 'electrum/locale'),
            ('C:\\Program Files (x86)\\ZBar\\bin\\', '.')
        ]
        datas += collect_data_files('trezorlib')
       t@@ -42,21 +41,21 @@ datas += collect_data_files('btchip')
        datas += collect_data_files('keepkeylib')
        
        # We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
       -a = Analysis([home+'electrum',
       -              home+'gui/qt/main_window.py',
       -              home+'gui/text.py',
       -              home+'lib/util.py',
       -              home+'lib/wallet.py',
       -              home+'lib/simple_config.py',
       -              home+'lib/bitcoin.py',
       -              home+'lib/dnssec.py',
       -              home+'lib/commands.py',
       -              home+'plugins/cosigner_pool/qt.py',
       -              home+'plugins/email_requests/qt.py',
       -              home+'plugins/trezor/client.py',
       -              home+'plugins/trezor/qt.py',
       -              home+'plugins/keepkey/qt.py',
       -              home+'plugins/ledger/qt.py',
       +a = Analysis([home+'run_electrum',
       +              home+'electrum/gui/qt/main_window.py',
       +              home+'electrum/gui/text.py',
       +              home+'electrum/util.py',
       +              home+'electrum/wallet.py',
       +              home+'electrum/simple_config.py',
       +              home+'electrum/bitcoin.py',
       +              home+'electrum/dnssec.py',
       +              home+'electrum/commands.py',
       +              home+'electrum/plugins/cosigner_pool/qt.py',
       +              home+'electrum/plugins/email_requests/qt.py',
       +              home+'electrum/plugins/trezor/client.py',
       +              home+'electrum/plugins/trezor/qt.py',
       +              home+'electrum/plugins/keepkey/qt.py',
       +              home+'electrum/plugins/ledger/qt.py',
                      #home+'packages/requests/utils.py'
                      ],
                     binaries=binaries,
       t@@ -68,7 +67,7 @@ a = Analysis([home+'electrum',
        
        # http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
        for d in a.datas:
       -    if 'pyconfig' in d[0]: 
       +    if 'pyconfig' in d[0]:
                a.datas.remove(d)
                break
        
       t@@ -85,7 +84,7 @@ exe_standalone = EXE(
            pyz,
            a.scripts,
            a.binaries,
       -    a.datas, 
       +    a.datas,
            name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + ".exe"),
            debug=False,
            strip=None,
   DIR diff --git a/contrib/make_apk b/contrib/make_apk
       t@@ -1,6 +1,6 @@
        #!/bin/bash
        
       -pushd ./gui/kivy/
       +pushd ./electrum/gui/kivy/
        
        if [[ -n "$1"  && "$1" == "release" ]] ; then
            echo -n Keystore Password:
   DIR diff --git a/contrib/make_locale b/contrib/make_locale
       t@@ -8,8 +8,7 @@ import requests
        os.chdir(os.path.dirname(os.path.realpath(__file__)))
        os.chdir('..')
        
       -code_directories = 'gui plugins lib'
       -cmd = "find {} -type f -name '*.py' -o -name '*.kv'".format(code_directories)
       +cmd = "find electrum -type f -name '*.py' -o -name '*.kv'"
        
        files = subprocess.check_output(cmd, shell=True)
        
       t@@ -19,13 +18,13 @@ with open("app.fil", "wb") as f:
        print("Found {} files to translate".format(len(files.splitlines())))
        
        # Generate fresh translation template
       -if not os.path.exists('lib/locale'):
       -    os.mkdir('lib/locale')
       -cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=lib/locale/messages.pot'
       +if not os.path.exists('electrum/locale'):
       +    os.mkdir('electrum/locale')
       +cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=electrum/locale/messages.pot'
        print('Generate template')
        os.system(cmd)
        
       -os.chdir('lib')
       +os.chdir('electrum')
        
        crowdin_identifier = 'electrum'
        crowdin_file_name = 'files[electrum-client/messages.pot]'
   DIR diff --git a/electrum b/electrum
       t@@ -1,480 +0,0 @@
       -#!/usr/bin/env python3
       -# -*- mode: python -*-
       -#
       -# Electrum - lightweight Bitcoin client
       -# Copyright (C) 2011 thomasv@gitorious
       -#
       -# Permission is hereby granted, free of charge, to any person
       -# obtaining a copy of this software and associated documentation files
       -# (the "Software"), to deal in the Software without restriction,
       -# including without limitation the rights to use, copy, modify, merge,
       -# publish, distribute, sublicense, and/or sell copies of the Software,
       -# and to permit persons to whom the Software is furnished to do so,
       -# subject to the following conditions:
       -#
       -# The above copyright notice and this permission notice shall be
       -# included in all copies or substantial portions of the Software.
       -#
       -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       -# SOFTWARE.
       -import os
       -import sys
       -
       -script_dir = os.path.dirname(os.path.realpath(__file__))
       -is_bundle = getattr(sys, 'frozen', False)
       -is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "electrum.desktop"))
       -is_android = 'ANDROID_DATA' in os.environ
       -
       -# move this back to gui/kivy/__init.py once plugins are moved
       -os.environ['KIVY_DATA_DIR'] = os.path.abspath(os.path.dirname(__file__)) + '/gui/kivy/data/'
       -
       -if is_local or is_android:
       -    sys.path.insert(0, os.path.join(script_dir, 'packages'))
       -
       -
       -def check_imports():
       -    # pure-python dependencies need to be imported here for pyinstaller
       -    try:
       -        import dns
       -        import pyaes
       -        import ecdsa
       -        import requests
       -        import qrcode
       -        import pbkdf2
       -        import google.protobuf
       -        import jsonrpclib
       -    except ImportError as e:
       -        sys.exit("Error: %s. Try 'sudo pip install <module-name>'"%str(e))
       -    # the following imports are for pyinstaller
       -    from google.protobuf import descriptor
       -    from google.protobuf import message
       -    from google.protobuf import reflection
       -    from google.protobuf import descriptor_pb2
       -    from jsonrpclib import SimpleJSONRPCServer
       -    # make sure that certificates are here
       -    assert os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH)
       -
       -
       -if not is_android:
       -    check_imports()
       -
       -# load local module as electrum
       -if is_local or is_android:
       -    import imp
       -    imp.load_module('electrum', *imp.find_module('lib'))
       -    imp.load_module('electrum_gui', *imp.find_module('gui'))
       -
       -
       -
       -from electrum import bitcoin, util
       -from electrum import constants
       -from electrum import SimpleConfig, Network
       -from electrum.wallet import Wallet, Imported_Wallet
       -from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption
       -from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled
       -from electrum.util import set_verbosity, InvalidPassword
       -from electrum.commands import get_parser, known_commands, Commands, config_variables
       -from electrum import daemon
       -from electrum import keystore
       -from electrum.mnemonic import Mnemonic
       -
       -# get password routine
       -def prompt_password(prompt, confirm=True):
       -    import getpass
       -    password = getpass.getpass(prompt, stream=None)
       -    if password and confirm:
       -        password2 = getpass.getpass("Confirm: ")
       -        if password != password2:
       -            sys.exit("Error: Passwords do not match.")
       -    if not password:
       -        password = None
       -    return password
       -
       -
       -
       -def run_non_RPC(config):
       -    cmdname = config.get('cmd')
       -
       -    storage = WalletStorage(config.get_wallet_path())
       -    if storage.file_exists():
       -        sys.exit("Error: Remove the existing wallet first!")
       -
       -    def password_dialog():
       -        return prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
       -
       -    if cmdname == 'restore':
       -        text = config.get('text').strip()
       -        passphrase = config.get('passphrase', '')
       -        password = password_dialog() if keystore.is_private(text) else None
       -        if keystore.is_address_list(text):
       -            wallet = Imported_Wallet(storage)
       -            for x in text.split():
       -                wallet.import_address(x)
       -        elif keystore.is_private_key_list(text):
       -            k = keystore.Imported_KeyStore({})
       -            storage.put('keystore', k.dump())
       -            storage.put('use_encryption', bool(password))
       -            wallet = Imported_Wallet(storage)
       -            for x in text.split():
       -                wallet.import_private_key(x, password)
       -            storage.write()
       -        else:
       -            if keystore.is_seed(text):
       -                k = keystore.from_seed(text, passphrase, False)
       -            elif keystore.is_master_key(text):
       -                k = keystore.from_master_key(text)
       -            else:
       -                sys.exit("Error: Seed or key not recognized")
       -            if password:
       -                k.update_password(None, password)
       -            storage.put('keystore', k.dump())
       -            storage.put('wallet_type', 'standard')
       -            storage.put('use_encryption', bool(password))
       -            storage.write()
       -            wallet = Wallet(storage)
       -        if not config.get('offline'):
       -            network = Network(config)
       -            network.start()
       -            wallet.start_threads(network)
       -            print_msg("Recovering wallet...")
       -            wallet.synchronize()
       -            wallet.wait_until_synchronized()
       -            wallet.stop_threads()
       -            # note: we don't wait for SPV
       -            msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
       -        else:
       -            msg = "This wallet was restored offline. It may contain more addresses than displayed."
       -        print_msg(msg)
       -
       -    elif cmdname == 'create':
       -        password = password_dialog()
       -        passphrase = config.get('passphrase', '')
       -        seed_type = 'segwit' if config.get('segwit') else 'standard'
       -        seed = Mnemonic('en').make_seed(seed_type)
       -        k = keystore.from_seed(seed, passphrase, False)
       -        storage.put('keystore', k.dump())
       -        storage.put('wallet_type', 'standard')
       -        wallet = Wallet(storage)
       -        wallet.update_password(None, password, True)
       -        wallet.synchronize()
       -        print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
       -        print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
       -
       -    wallet.storage.write()
       -    print_msg("Wallet saved in '%s'" % wallet.storage.path)
       -    sys.exit(0)
       -
       -
       -def init_daemon(config_options):
       -    config = SimpleConfig(config_options)
       -    storage = WalletStorage(config.get_wallet_path())
       -    if not storage.file_exists():
       -        print_msg("Error: Wallet file not found.")
       -        print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
       -        sys.exit(0)
       -    if storage.is_encrypted():
       -        if storage.is_encrypted_with_hw_device():
       -            plugins = init_plugins(config, 'cmdline')
       -            password = get_password_for_hw_device_encrypted_storage(plugins)
       -        elif config.get('password'):
       -            password = config.get('password')
       -        else:
       -            password = prompt_password('Password:', False)
       -            if not password:
       -                print_msg("Error: Password required")
       -                sys.exit(1)
       -    else:
       -        password = None
       -    config_options['password'] = password
       -
       -
       -def init_cmdline(config_options, server):
       -    config = SimpleConfig(config_options)
       -    cmdname = config.get('cmd')
       -    cmd = known_commands[cmdname]
       -
       -    if cmdname == 'signtransaction' and config.get('privkey'):
       -        cmd.requires_wallet = False
       -        cmd.requires_password = False
       -
       -    if cmdname in ['payto', 'paytomany'] and config.get('unsigned'):
       -        cmd.requires_password = False
       -
       -    if cmdname in ['payto', 'paytomany'] and config.get('broadcast'):
       -        cmd.requires_network = True
       -
       -    # instantiate wallet for command-line
       -    storage = WalletStorage(config.get_wallet_path())
       -
       -    if cmd.requires_wallet and not storage.file_exists():
       -        print_msg("Error: Wallet file not found.")
       -        print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
       -        sys.exit(0)
       -
       -    # important warning
       -    if cmd.name in ['getprivatekeys']:
       -        print_stderr("WARNING: ALL your private keys are secret.")
       -        print_stderr("Exposing a single private key can compromise your entire wallet!")
       -        print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
       -
       -    # commands needing password
       -    if (cmd.requires_wallet and storage.is_encrypted() and server is None)\
       -       or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted())):
       -        if storage.is_encrypted_with_hw_device():
       -            # this case is handled later in the control flow
       -            password = None
       -        elif config.get('password'):
       -            password = config.get('password')
       -        else:
       -            password = prompt_password('Password:', False)
       -            if not password:
       -                print_msg("Error: Password required")
       -                sys.exit(1)
       -    else:
       -        password = None
       -
       -    config_options['password'] = password
       -
       -    if cmd.name == 'password':
       -        new_password = prompt_password('New password:')
       -        config_options['new_password'] = new_password
       -
       -    return cmd, password
       -
       -
       -def get_connected_hw_devices(plugins):
       -    support = plugins.get_hardware_support()
       -    if not support:
       -        print_msg('No hardware wallet support found on your system.')
       -        sys.exit(1)
       -    # scan devices
       -    devices = []
       -    devmgr = plugins.device_manager
       -    for name, description, plugin in support:
       -        try:
       -            u = devmgr.unpaired_device_infos(None, plugin)
       -        except:
       -            devmgr.print_error("error", name)
       -            continue
       -        devices += list(map(lambda x: (name, x), u))
       -    return devices
       -
       -
       -def get_password_for_hw_device_encrypted_storage(plugins):
       -    devices = get_connected_hw_devices(plugins)
       -    if len(devices) == 0:
       -        print_msg("Error: No connected hw device found. Cannot decrypt this wallet.")
       -        sys.exit(1)
       -    elif len(devices) > 1:
       -        print_msg("Warning: multiple hardware devices detected. "
       -                  "The first one will be used to decrypt the wallet.")
       -    # FIXME we use the "first" device, in case of multiple ones
       -    name, device_info = devices[0]
       -    plugin = plugins.get_plugin(name)
       -    derivation = get_derivation_used_for_hw_device_encryption()
       -    try:
       -        xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler)
       -    except UserCancelled:
       -        sys.exit(0)
       -    password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
       -    return password
       -
       -
       -def run_offline_command(config, config_options, plugins):
       -    cmdname = config.get('cmd')
       -    cmd = known_commands[cmdname]
       -    password = config_options.get('password')
       -    if cmd.requires_wallet:
       -        storage = WalletStorage(config.get_wallet_path())
       -        if storage.is_encrypted():
       -            if storage.is_encrypted_with_hw_device():
       -                password = get_password_for_hw_device_encrypted_storage(plugins)
       -                config_options['password'] = password
       -            storage.decrypt(password)
       -        wallet = Wallet(storage)
       -    else:
       -        wallet = None
       -    # check password
       -    if cmd.requires_password and wallet.has_password():
       -        try:
       -            seed = wallet.check_password(password)
       -        except InvalidPassword:
       -            print_msg("Error: This password does not decode this wallet.")
       -            sys.exit(1)
       -    if cmd.requires_network:
       -        print_msg("Warning: running command offline")
       -    # arguments passed to function
       -    args = [config.get(x) for x in cmd.params]
       -    # decode json arguments
       -    if cmdname not in ('setconfig',):
       -        args = list(map(json_decode, args))
       -    # options
       -    kwargs = {}
       -    for x in cmd.options:
       -        kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))
       -    cmd_runner = Commands(config, wallet, None)
       -    func = getattr(cmd_runner, cmd.name)
       -    result = func(*args, **kwargs)
       -    # save wallet
       -    if wallet:
       -        wallet.storage.write()
       -    return result
       -
       -def init_plugins(config, gui_name):
       -    from electrum.plugins import Plugins
       -    return Plugins(config, is_local or is_android, gui_name)
       -
       -
       -if __name__ == '__main__':
       -    # The hook will only be used in the Qt GUI right now
       -    util.setup_thread_excepthook()
       -    # on macOS, delete Process Serial Number arg generated for apps launched in Finder
       -    sys.argv = list(filter(lambda x: not x.startswith('-psn'), sys.argv))
       -
       -    # old 'help' syntax
       -    if len(sys.argv) > 1 and sys.argv[1] == 'help':
       -        sys.argv.remove('help')
       -        sys.argv.append('-h')
       -
       -    # read arguments from stdin pipe and prompt
       -    for i, arg in enumerate(sys.argv):
       -        if arg == '-':
       -            if not sys.stdin.isatty():
       -                sys.argv[i] = sys.stdin.read()
       -                break
       -            else:
       -                raise Exception('Cannot get argument from stdin')
       -        elif arg == '?':
       -            sys.argv[i] = input("Enter argument:")
       -        elif arg == ':':
       -            sys.argv[i] = prompt_password('Enter argument (will not echo):', False)
       -
       -    # parse command line
       -    parser = get_parser()
       -    args = parser.parse_args()
       -
       -    # config is an object passed to the various constructors (wallet, interface, gui)
       -    if is_android:
       -        config_options = {
       -            'verbose': True,
       -            'cmd': 'gui',
       -            'gui': 'kivy',
       -        }
       -    else:
       -        config_options = args.__dict__
       -        f = lambda key: config_options[key] is not None and key not in config_variables.get(args.cmd, {}).keys()
       -        config_options = {key: config_options[key] for key in filter(f, config_options.keys())}
       -        if config_options.get('server'):
       -            config_options['auto_connect'] = False
       -
       -    config_options['cwd'] = os.getcwd()
       -
       -    # fixme: this can probably be achieved with a runtime hook (pyinstaller)
       -    if is_bundle and os.path.exists(os.path.join(sys._MEIPASS, 'is_portable')):
       -        config_options['portable'] = True
       -
       -    if config_options.get('portable'):
       -        config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
       -
       -    # kivy sometimes freezes when we write to sys.stderr
       -    set_verbosity(config_options.get('verbose') and config_options.get('gui')!='kivy')
       -
       -    # check uri
       -    uri = config_options.get('url')
       -    if uri:
       -        if not uri.startswith('bitcoin:'):
       -            print_stderr('unknown command:', uri)
       -            sys.exit(1)
       -        config_options['url'] = uri
       -
       -    # todo: defer this to gui
       -    config = SimpleConfig(config_options)
       -    cmdname = config.get('cmd')
       -
       -    if config.get('testnet'):
       -        constants.set_testnet()
       -    elif config.get('regtest'):
       -        constants.set_regtest()
       -    elif config.get('simnet'):
       -        constants.set_simnet()
       -
       -    # run non-RPC commands separately
       -    if cmdname in ['create', 'restore']:
       -        run_non_RPC(config)
       -        sys.exit(0)
       -
       -    if cmdname == 'gui':
       -        fd, server = daemon.get_fd_or_server(config)
       -        if fd is not None:
       -            plugins = init_plugins(config, config.get('gui', 'qt'))
       -            d = daemon.Daemon(config, fd, True)
       -            d.start()
       -            d.init_gui(config, plugins)
       -            sys.exit(0)
       -        else:
       -            result = server.gui(config_options)
       -
       -    elif cmdname == 'daemon':
       -        subcommand = config.get('subcommand')
       -        if subcommand in ['load_wallet']:
       -            init_daemon(config_options)
       -
       -        if subcommand in [None, 'start']:
       -            fd, server = daemon.get_fd_or_server(config)
       -            if fd is not None:
       -                if subcommand == 'start':
       -                    pid = os.fork()
       -                    if pid:
       -                        print_stderr("starting daemon (PID %d)" % pid)
       -                        sys.exit(0)
       -                init_plugins(config, 'cmdline')
       -                d = daemon.Daemon(config, fd, False)
       -                d.start()
       -                if config.get('websocket_server'):
       -                    from electrum import websockets
       -                    websockets.WebSocketServer(config, d.network).start()
       -                if config.get('requests_dir'):
       -                    path = os.path.join(config.get('requests_dir'), 'index.html')
       -                    if not os.path.exists(path):
       -                        print("Requests directory not configured.")
       -                        print("You can configure it using https://github.com/spesmilo/electrum-merchant")
       -                        sys.exit(1)
       -                d.join()
       -                sys.exit(0)
       -            else:
       -                result = server.daemon(config_options)
       -        else:
       -            server = daemon.get_server(config)
       -            if server is not None:
       -                result = server.daemon(config_options)
       -            else:
       -                print_msg("Daemon not running")
       -                sys.exit(1)
       -    else:
       -        # command line
       -        server = daemon.get_server(config)
       -        init_cmdline(config_options, server)
       -        if server is not None:
       -            result = server.run_cmdline(config_options)
       -        else:
       -            cmd = known_commands[cmdname]
       -            if cmd.requires_network:
       -                print_msg("Daemon not running; try 'electrum daemon start'")
       -                sys.exit(1)
       -            else:
       -                plugins = init_plugins(config, 'cmdline')
       -                result = run_offline_command(config, config_options, plugins)
       -                # print result
       -    if isinstance(result, str):
       -        print_msg(result)
       -    elif type(result) is dict and result.get('error'):
       -        print_stderr(result.get('error'))
       -    elif result is not None:
       -        print_msg(json_encode(result))
       -    sys.exit(0)
   DIR diff --git a/electrum-env b/electrum-env
       t@@ -22,6 +22,6 @@ fi
        
        export PYTHONPATH="/usr/local/lib/python${PYTHON_VER}/site-packages:$PYTHONPATH"
        
       -./electrum "$@"
       +./run_electrum "$@"
        
        deactivate
   DIR diff --git a/electrum/__init__.py b/electrum/__init__.py
       t@@ -0,0 +1,14 @@
       +from .version import ELECTRUM_VERSION
       +from .util import format_satoshis, print_msg, print_error, set_verbosity
       +from .wallet import Synchronizer, Wallet
       +from .storage import WalletStorage
       +from .coinchooser import COIN_CHOOSERS
       +from .network import Network, pick_random_server
       +from .interface import Connection, Interface
       +from .simple_config import SimpleConfig, get_config, set_config
       +from . import bitcoin
       +from . import transaction
       +from . import daemon
       +from .transaction import Transaction
       +from .plugin import BasePlugin
       +from .commands import Commands, known_commands
   DIR diff --git a/electrum/base_crash_reporter.py b/electrum/base_crash_reporter.py
       t@@ -0,0 +1,128 @@
       +# Electrum - lightweight Bitcoin client
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import json
       +import locale
       +import traceback
       +import subprocess
       +import sys
       +import os
       +
       +import requests
       +
       +from .version import ELECTRUM_VERSION
       +from .import constants
       +from .i18n import _
       +
       +
       +class BaseCrashReporter(object):
       +    report_server = "https://crashhub.electrum.org"
       +    config_key = "show_crash_reporter"
       +    issue_template = """<h2>Traceback</h2>
       +<pre>
       +{traceback}
       +</pre>
       +
       +<h2>Additional information</h2>
       +<ul>
       +  <li>Electrum version: {app_version}</li>
       +  <li>Python version: {python_version}</li>
       +  <li>Operating system: {os}</li>
       +  <li>Wallet type: {wallet_type}</li>
       +  <li>Locale: {locale}</li>
       +</ul>
       +    """
       +    CRASH_MESSAGE = _('Something went wrong while executing Electrum.')
       +    CRASH_TITLE = _('Sorry!')
       +    REQUEST_HELP_MESSAGE = _('To help us diagnose and fix the problem, you can send us a bug report that contains '
       +                             'useful debug information:')
       +    DESCRIBE_ERROR_MESSAGE = _("Please briefly describe what led to the error (optional):")
       +    ASK_CONFIRM_SEND = _("Do you want to send this report?")
       +
       +    def __init__(self, exctype, value, tb):
       +        self.exc_args = (exctype, value, tb)
       +
       +    def send_report(self, endpoint="/crash"):
       +        if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server:
       +            # Gah! Some kind of altcoin wants to send us crash reports.
       +            raise Exception(_("Missing report URL."))
       +        report = self.get_traceback_info()
       +        report.update(self.get_additional_info())
       +        report = json.dumps(report)
       +        response = requests.post(BaseCrashReporter.report_server + endpoint, data=report)
       +        return response
       +
       +    def get_traceback_info(self):
       +        exc_string = str(self.exc_args[1])
       +        stack = traceback.extract_tb(self.exc_args[2])
       +        readable_trace = "".join(traceback.format_list(stack))
       +        id = {
       +            "file": stack[-1].filename,
       +            "name": stack[-1].name,
       +            "type": self.exc_args[0].__name__
       +        }
       +        return {
       +            "exc_string": exc_string,
       +            "stack": readable_trace,
       +            "id": id
       +        }
       +
       +    def get_additional_info(self):
       +        args = {
       +            "app_version": ELECTRUM_VERSION,
       +            "python_version": sys.version,
       +            "os": self.get_os_version(),
       +            "wallet_type": "unknown",
       +            "locale": locale.getdefaultlocale()[0] or "?",
       +            "description": self.get_user_description()
       +        }
       +        try:
       +            args["wallet_type"] = self.get_wallet_type()
       +        except:
       +            # Maybe the wallet isn't loaded yet
       +            pass
       +        try:
       +            args["app_version"] = self.get_git_version()
       +        except:
       +            # This is probably not running from source
       +            pass
       +        return args
       +
       +    @staticmethod
       +    def get_git_version():
       +        dir = os.path.dirname(os.path.realpath(sys.argv[0]))
       +        version = subprocess.check_output(
       +            ['git', 'describe', '--always', '--dirty'], cwd=dir)
       +        return str(version, "utf8").strip()
       +
       +    def get_report_string(self):
       +        info = self.get_additional_info()
       +        info["traceback"] = "".join(traceback.format_exception(*self.exc_args))
       +        return self.issue_template.format(**info)
       +
       +    def get_user_description(self):
       +        raise NotImplementedError
       +
       +    def get_wallet_type(self):
       +        raise NotImplementedError
       +
       +    def get_os_version(self):
       +        raise NotImplementedError 
   DIR diff --git a/lib/base_wizard.py b/electrum/base_wizard.py
   DIR diff --git a/lib/bitcoin.py b/electrum/bitcoin.py
   DIR diff --git a/lib/blockchain.py b/electrum/blockchain.py
   DIR diff --git a/lib/checkpoints.json b/electrum/checkpoints.json
   DIR diff --git a/lib/checkpoints_testnet.json b/electrum/checkpoints_testnet.json
   DIR diff --git a/lib/coinchooser.py b/electrum/coinchooser.py
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -0,0 +1,892 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2011 thomasv@gitorious
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +import sys
       +import datetime
       +import copy
       +import argparse
       +import json
       +import ast
       +import base64
       +from functools import wraps
       +from decimal import Decimal
       +
       +from .import util, ecc
       +from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode
       +from . import bitcoin
       +from .bitcoin import is_address,  hash_160, COIN, TYPE_ADDRESS
       +from .i18n import _
       +from .transaction import Transaction, multisig_script
       +from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
       +from .plugin import run_hook
       +
       +known_commands = {}
       +
       +
       +def satoshis(amount):
       +    # satoshi conversion must not be performed by the parser
       +    return int(COIN*Decimal(amount)) if amount not in ['!', None] else amount
       +
       +
       +class Command:
       +    def __init__(self, func, s):
       +        self.name = func.__name__
       +        self.requires_network = 'n' in s
       +        self.requires_wallet = 'w' in s
       +        self.requires_password = 'p' in s
       +        self.description = func.__doc__
       +        self.help = self.description.split('.')[0] if self.description else None
       +        varnames = func.__code__.co_varnames[1:func.__code__.co_argcount]
       +        self.defaults = func.__defaults__
       +        if self.defaults:
       +            n = len(self.defaults)
       +            self.params = list(varnames[:-n])
       +            self.options = list(varnames[-n:])
       +        else:
       +            self.params = list(varnames)
       +            self.options = []
       +            self.defaults = []
       +
       +
       +def command(s):
       +    def decorator(func):
       +        global known_commands
       +        name = func.__name__
       +        known_commands[name] = Command(func, s)
       +        @wraps(func)
       +        def func_wrapper(*args, **kwargs):
       +            c = known_commands[func.__name__]
       +            wallet = args[0].wallet
       +            password = kwargs.get('password')
       +            if c.requires_wallet and wallet is None:
       +                raise Exception("wallet not loaded. Use 'electrum daemon load_wallet'")
       +            if c.requires_password and password is None and wallet.has_password():
       +                return {'error': 'Password required' }
       +            return func(*args, **kwargs)
       +        return func_wrapper
       +    return decorator
       +
       +
       +class Commands:
       +
       +    def __init__(self, config, wallet, network, callback = None):
       +        self.config = config
       +        self.wallet = wallet
       +        self.network = network
       +        self._callback = callback
       +
       +    def _run(self, method, args, password_getter):
       +        # this wrapper is called from the python console
       +        cmd = known_commands[method]
       +        if cmd.requires_password and self.wallet.has_password():
       +            password = password_getter()
       +            if password is None:
       +                return
       +        else:
       +            password = None
       +
       +        f = getattr(self, method)
       +        if cmd.requires_password:
       +            result = f(*args, **{'password':password})
       +        else:
       +            result = f(*args)
       +
       +        if self._callback:
       +            self._callback()
       +        return result
       +
       +    @command('')
       +    def commands(self):
       +        """List of commands"""
       +        return ' '.join(sorted(known_commands.keys()))
       +
       +    @command('')
       +    def create(self, segwit=False):
       +        """Create a new wallet"""
       +        raise Exception('Not a JSON-RPC command')
       +
       +    @command('wn')
       +    def restore(self, text):
       +        """Restore a wallet from text. Text can be a seed phrase, a master
       +        public key, a master private key, a list of bitcoin addresses
       +        or bitcoin private keys. If you want to be prompted for your
       +        seed, type '?' or ':' (concealed) """
       +        raise Exception('Not a JSON-RPC command')
       +
       +    @command('wp')
       +    def password(self, password=None, new_password=None):
       +        """Change wallet password. """
       +        if self.wallet.storage.is_encrypted_with_hw_device() and new_password:
       +            raise Exception("Can't change the password of a wallet encrypted with a hw device.")
       +        b = self.wallet.storage.is_encrypted()
       +        self.wallet.update_password(password, new_password, b)
       +        self.wallet.storage.write()
       +        return {'password':self.wallet.has_password()}
       +
       +    @command('')
       +    def getconfig(self, key):
       +        """Return a configuration variable. """
       +        return self.config.get(key)
       +
       +    @classmethod
       +    def _setconfig_normalize_value(cls, key, value):
       +        if key not in ('rpcuser', 'rpcpassword'):
       +            value = json_decode(value)
       +            try:
       +                value = ast.literal_eval(value)
       +            except:
       +                pass
       +        return value
       +
       +    @command('')
       +    def setconfig(self, key, value):
       +        """Set a configuration variable. 'value' may be a string or a Python expression."""
       +        value = self._setconfig_normalize_value(key, value)
       +        self.config.set_key(key, value)
       +        return True
       +
       +    @command('')
       +    def make_seed(self, nbits=132, language=None, segwit=False):
       +        """Create a seed"""
       +        from .mnemonic import Mnemonic
       +        t = 'segwit' if segwit else 'standard'
       +        s = Mnemonic(language).make_seed(t, nbits)
       +        return s
       +
       +    @command('n')
       +    def getaddresshistory(self, address):
       +        """Return the transaction history of any address. Note: This is a
       +        walletless server query, results are not checked by SPV.
       +        """
       +        sh = bitcoin.address_to_scripthash(address)
       +        return self.network.get_history_for_scripthash(sh)
       +
       +    @command('w')
       +    def listunspent(self):
       +        """List unspent outputs. Returns the list of unspent transaction
       +        outputs in your wallet."""
       +        l = copy.deepcopy(self.wallet.get_utxos(exclude_frozen=False))
       +        for i in l:
       +            v = i["value"]
       +            i["value"] = str(Decimal(v)/COIN) if v is not None else None
       +        return l
       +
       +    @command('n')
       +    def getaddressunspent(self, address):
       +        """Returns the UTXO list of any address. Note: This
       +        is a walletless server query, results are not checked by SPV.
       +        """
       +        sh = bitcoin.address_to_scripthash(address)
       +        return self.network.listunspent_for_scripthash(sh)
       +
       +    @command('')
       +    def serialize(self, jsontx):
       +        """Create a transaction from json inputs.
       +        Inputs must have a redeemPubkey.
       +        Outputs must be a list of {'address':address, 'value':satoshi_amount}.
       +        """
       +        keypairs = {}
       +        inputs = jsontx.get('inputs')
       +        outputs = jsontx.get('outputs')
       +        locktime = jsontx.get('lockTime', 0)
       +        for txin in inputs:
       +            if txin.get('output'):
       +                prevout_hash, prevout_n = txin['output'].split(':')
       +                txin['prevout_n'] = int(prevout_n)
       +                txin['prevout_hash'] = prevout_hash
       +            sec = txin.get('privkey')
       +            if sec:
       +                txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
       +                pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
       +                keypairs[pubkey] = privkey, compressed
       +                txin['type'] = txin_type
       +                txin['x_pubkeys'] = [pubkey]
       +                txin['signatures'] = [None]
       +                txin['num_sig'] = 1
       +
       +        outputs = [(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]
       +        tx = Transaction.from_io(inputs, outputs, locktime=locktime)
       +        tx.sign(keypairs)
       +        return tx.as_dict()
       +
       +    @command('wp')
       +    def signtransaction(self, tx, privkey=None, password=None):
       +        """Sign a transaction. The wallet keys will be used unless a private key is provided."""
       +        tx = Transaction(tx)
       +        if privkey:
       +            txin_type, privkey2, compressed = bitcoin.deserialize_privkey(privkey)
       +            pubkey_bytes = ecc.ECPrivkey(privkey2).get_public_key_bytes(compressed=compressed)
       +            h160 = bitcoin.hash_160(pubkey_bytes)
       +            x_pubkey = 'fd' + bh2u(b'\x00' + h160)
       +            tx.sign({x_pubkey:(privkey2, compressed)})
       +        else:
       +            self.wallet.sign_transaction(tx, password)
       +        return tx.as_dict()
       +
       +    @command('')
       +    def deserialize(self, tx):
       +        """Deserialize a serialized transaction"""
       +        tx = Transaction(tx)
       +        return tx.deserialize()
       +
       +    @command('n')
       +    def broadcast(self, tx):
       +        """Broadcast a transaction to the network. """
       +        tx = Transaction(tx)
       +        return self.network.broadcast_transaction(tx)
       +
       +    @command('')
       +    def createmultisig(self, num, pubkeys):
       +        """Create multisig address"""
       +        assert isinstance(pubkeys, list), (type(num), type(pubkeys))
       +        redeem_script = multisig_script(pubkeys, num)
       +        address = bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
       +        return {'address':address, 'redeemScript':redeem_script}
       +
       +    @command('w')
       +    def freeze(self, address):
       +        """Freeze address. Freeze the funds at one of your wallet\'s addresses"""
       +        return self.wallet.set_frozen_state([address], True)
       +
       +    @command('w')
       +    def unfreeze(self, address):
       +        """Unfreeze address. Unfreeze the funds at one of your wallet\'s address"""
       +        return self.wallet.set_frozen_state([address], False)
       +
       +    @command('wp')
       +    def getprivatekeys(self, address, password=None):
       +        """Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses."""
       +        if isinstance(address, str):
       +            address = address.strip()
       +        if is_address(address):
       +            return self.wallet.export_private_key(address, password)[0]
       +        domain = address
       +        return [self.wallet.export_private_key(address, password)[0] for address in domain]
       +
       +    @command('w')
       +    def ismine(self, address):
       +        """Check if address is in wallet. Return true if and only address is in wallet"""
       +        return self.wallet.is_mine(address)
       +
       +    @command('')
       +    def dumpprivkeys(self):
       +        """Deprecated."""
       +        return "This command is deprecated. Use a pipe instead: 'electrum listaddresses | electrum getprivatekeys - '"
       +
       +    @command('')
       +    def validateaddress(self, address):
       +        """Check that an address is valid. """
       +        return is_address(address)
       +
       +    @command('w')
       +    def getpubkeys(self, address):
       +        """Return the public keys for a wallet address. """
       +        return self.wallet.get_public_keys(address)
       +
       +    @command('w')
       +    def getbalance(self):
       +        """Return the balance of your wallet. """
       +        c, u, x = self.wallet.get_balance()
       +        out = {"confirmed": str(Decimal(c)/COIN)}
       +        if u:
       +            out["unconfirmed"] = str(Decimal(u)/COIN)
       +        if x:
       +            out["unmatured"] = str(Decimal(x)/COIN)
       +        return out
       +
       +    @command('n')
       +    def getaddressbalance(self, address):
       +        """Return the balance of any address. Note: This is a walletless
       +        server query, results are not checked by SPV.
       +        """
       +        sh = bitcoin.address_to_scripthash(address)
       +        out = self.network.get_balance_for_scripthash(sh)
       +        out["confirmed"] =  str(Decimal(out["confirmed"])/COIN)
       +        out["unconfirmed"] =  str(Decimal(out["unconfirmed"])/COIN)
       +        return out
       +
       +    @command('n')
       +    def getmerkle(self, txid, height):
       +        """Get Merkle branch of a transaction included in a block. Electrum
       +        uses this to verify transactions (Simple Payment Verification)."""
       +        return self.network.get_merkle_for_transaction(txid, int(height))
       +
       +    @command('n')
       +    def getservers(self):
       +        """Return the list of available servers"""
       +        return self.network.get_servers()
       +
       +    @command('')
       +    def version(self):
       +        """Return the version of Electrum."""
       +        from .version import ELECTRUM_VERSION
       +        return ELECTRUM_VERSION
       +
       +    @command('w')
       +    def getmpk(self):
       +        """Get master public key. Return your wallet\'s master public key"""
       +        return self.wallet.get_master_public_key()
       +
       +    @command('wp')
       +    def getmasterprivate(self, password=None):
       +        """Get master private key. Return your wallet\'s master private key"""
       +        return str(self.wallet.keystore.get_master_private_key(password))
       +
       +    @command('wp')
       +    def getseed(self, password=None):
       +        """Get seed phrase. Print the generation seed of your wallet."""
       +        s = self.wallet.get_seed(password)
       +        return s
       +
       +    @command('wp')
       +    def importprivkey(self, privkey, password=None):
       +        """Import a private key."""
       +        if not self.wallet.can_import_privkey():
       +            return "Error: This type of wallet cannot import private keys. Try to create a new wallet with that key."
       +        try:
       +            addr = self.wallet.import_private_key(privkey, password)
       +            out = "Keypair imported: " + addr
       +        except BaseException as e:
       +            out = "Error: " + str(e)
       +        return out
       +
       +    def _resolver(self, x):
       +        if x is None:
       +            return None
       +        out = self.wallet.contacts.resolve(x)
       +        if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:
       +            raise Exception('cannot verify alias', x)
       +        return out['address']
       +
       +    @command('n')
       +    def sweep(self, privkey, destination, fee=None, nocheck=False, imax=100):
       +        """Sweep private keys. Returns a transaction that spends UTXOs from
       +        privkey to a destination address. The transaction is not
       +        broadcasted."""
       +        from .wallet import sweep
       +        tx_fee = satoshis(fee)
       +        privkeys = privkey.split()
       +        self.nocheck = nocheck
       +        #dest = self._resolver(destination)
       +        tx = sweep(privkeys, self.network, self.config, destination, tx_fee, imax)
       +        return tx.as_dict() if tx else None
       +
       +    @command('wp')
       +    def signmessage(self, address, message, password=None):
       +        """Sign a message with a key. Use quotes if your message contains
       +        whitespaces"""
       +        sig = self.wallet.sign_message(address, message, password)
       +        return base64.b64encode(sig).decode('ascii')
       +
       +    @command('')
       +    def verifymessage(self, address, signature, message):
       +        """Verify a signature."""
       +        sig = base64.b64decode(signature)
       +        message = util.to_bytes(message)
       +        return ecc.verify_message_with_address(address, sig, message)
       +
       +    def _mktx(self, outputs, fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime=None):
       +        self.nocheck = nocheck
       +        change_addr = self._resolver(change_addr)
       +        domain = None if domain is None else map(self._resolver, domain)
       +        final_outputs = []
       +        for address, amount in outputs:
       +            address = self._resolver(address)
       +            amount = satoshis(amount)
       +            final_outputs.append((TYPE_ADDRESS, address, amount))
       +
       +        coins = self.wallet.get_spendable_coins(domain, self.config)
       +        tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
       +        if locktime != None: 
       +            tx.locktime = locktime
       +        if rbf is None:
       +            rbf = self.config.get('use_rbf', True)
       +        if rbf:
       +            tx.set_rbf(True)
       +        if not unsigned:
       +            self.wallet.sign_transaction(tx, password)
       +        return tx
       +
       +    @command('wp')
       +    def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=None, password=None, locktime=None):
       +        """Create a transaction. """
       +        tx_fee = satoshis(fee)
       +        domain = from_addr.split(',') if from_addr else None
       +        tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime)
       +        return tx.as_dict()
       +
       +    @command('wp')
       +    def paytomany(self, outputs, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=None, password=None, locktime=None):
       +        """Create a multi-output transaction. """
       +        tx_fee = satoshis(fee)
       +        domain = from_addr.split(',') if from_addr else None
       +        tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime)
       +        return tx.as_dict()
       +
       +    @command('w')
       +    def history(self, year=None, show_addresses=False, show_fiat=False):
       +        """Wallet history. Returns the transaction history of your wallet."""
       +        kwargs = {'show_addresses': show_addresses}
       +        if year:
       +            import time
       +            start_date = datetime.datetime(year, 1, 1)
       +            end_date = datetime.datetime(year+1, 1, 1)
       +            kwargs['from_timestamp'] = time.mktime(start_date.timetuple())
       +            kwargs['to_timestamp'] = time.mktime(end_date.timetuple())
       +        if show_fiat:
       +            from .exchange_rate import FxThread
       +            fx = FxThread(self.config, None)
       +            kwargs['fx'] = fx
       +        return json_encode(self.wallet.get_full_history(**kwargs))
       +
       +    @command('w')
       +    def setlabel(self, key, label):
       +        """Assign a label to an item. Item may be a bitcoin address or a
       +        transaction ID"""
       +        self.wallet.set_label(key, label)
       +
       +    @command('w')
       +    def listcontacts(self):
       +        """Show your list of contacts"""
       +        return self.wallet.contacts
       +
       +    @command('w')
       +    def getalias(self, key):
       +        """Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record."""
       +        return self.wallet.contacts.resolve(key)
       +
       +    @command('w')
       +    def searchcontacts(self, query):
       +        """Search through contacts, return matching entries. """
       +        results = {}
       +        for key, value in self.wallet.contacts.items():
       +            if query.lower() in key.lower():
       +                results[key] = value
       +        return results
       +
       +    @command('w')
       +    def listaddresses(self, receiving=False, change=False, labels=False, frozen=False, unused=False, funded=False, balance=False):
       +        """List wallet addresses. Returns the list of all addresses in your wallet. Use optional arguments to filter the results."""
       +        out = []
       +        for addr in self.wallet.get_addresses():
       +            if frozen and not self.wallet.is_frozen(addr):
       +                continue
       +            if receiving and self.wallet.is_change(addr):
       +                continue
       +            if change and not self.wallet.is_change(addr):
       +                continue
       +            if unused and self.wallet.is_used(addr):
       +                continue
       +            if funded and self.wallet.is_empty(addr):
       +                continue
       +            item = addr
       +            if labels or balance:
       +                item = (item,)
       +            if balance:
       +                item += (format_satoshis(sum(self.wallet.get_addr_balance(addr))),)
       +            if labels:
       +                item += (repr(self.wallet.labels.get(addr, '')),)
       +            out.append(item)
       +        return out
       +
       +    @command('n')
       +    def gettransaction(self, txid):
       +        """Retrieve a transaction. """
       +        if self.wallet and txid in self.wallet.transactions:
       +            tx = self.wallet.transactions[txid]
       +        else:
       +            raw = self.network.get_transaction(txid)
       +            if raw:
       +                tx = Transaction(raw)
       +            else:
       +                raise Exception("Unknown transaction")
       +        return tx.as_dict()
       +
       +    @command('')
       +    def encrypt(self, pubkey, message):
       +        """Encrypt a message with a public key. Use quotes if the message contains whitespaces."""
       +        public_key = ecc.ECPubkey(bfh(pubkey))
       +        encrypted = public_key.encrypt_message(message)
       +        return encrypted
       +
       +    @command('wp')
       +    def decrypt(self, pubkey, encrypted, password=None):
       +        """Decrypt a message encrypted with a public key."""
       +        return self.wallet.decrypt_message(pubkey, encrypted, password)
       +
       +    def _format_request(self, out):
       +        pr_str = {
       +            PR_UNKNOWN: 'Unknown',
       +            PR_UNPAID: 'Pending',
       +            PR_PAID: 'Paid',
       +            PR_EXPIRED: 'Expired',
       +        }
       +        out['amount (BTC)'] = format_satoshis(out.get('amount'))
       +        out['status'] = pr_str[out.get('status', PR_UNKNOWN)]
       +        return out
       +
       +    @command('w')
       +    def getrequest(self, key):
       +        """Return a payment request"""
       +        r = self.wallet.get_payment_request(key, self.config)
       +        if not r:
       +            raise Exception("Request not found")
       +        return self._format_request(r)
       +
       +    #@command('w')
       +    #def ackrequest(self, serialized):
       +    #    """<Not implemented>"""
       +    #    pass
       +
       +    @command('w')
       +    def listrequests(self, pending=False, expired=False, paid=False):
       +        """List the payment requests you made."""
       +        out = self.wallet.get_sorted_requests(self.config)
       +        if pending:
       +            f = PR_UNPAID
       +        elif expired:
       +            f = PR_EXPIRED
       +        elif paid:
       +            f = PR_PAID
       +        else:
       +            f = None
       +        if f is not None:
       +            out = list(filter(lambda x: x.get('status')==f, out))
       +        return list(map(self._format_request, out))
       +
       +    @command('w')
       +    def createnewaddress(self):
       +        """Create a new receiving address, beyond the gap limit of the wallet"""
       +        return self.wallet.create_new_address(False)
       +
       +    @command('w')
       +    def getunusedaddress(self):
       +        """Returns the first unused address of the wallet, or None if all addresses are used.
       +        An address is considered as used if it has received a transaction, or if it is used in a payment request."""
       +        return self.wallet.get_unused_address()
       +
       +    @command('w')
       +    def addrequest(self, amount, memo='', expiration=None, force=False):
       +        """Create a payment request, using the first unused address of the wallet.
       +        The address will be considered as used after this operation.
       +        If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet."""
       +        addr = self.wallet.get_unused_address()
       +        if addr is None:
       +            if force:
       +                addr = self.wallet.create_new_address(False)
       +            else:
       +                return False
       +        amount = satoshis(amount)
       +        expiration = int(expiration) if expiration else None
       +        req = self.wallet.make_payment_request(addr, amount, memo, expiration)
       +        self.wallet.add_payment_request(req, self.config)
       +        out = self.wallet.get_payment_request(addr, self.config)
       +        return self._format_request(out)
       +
       +    @command('w')
       +    def addtransaction(self, tx):
       +        """ Add a transaction to the wallet history """
       +        tx = Transaction(tx)
       +        if not self.wallet.add_transaction(tx.txid(), tx):
       +            return False
       +        self.wallet.save_transactions()
       +        return tx.txid()
       +
       +    @command('wp')
       +    def signrequest(self, address, password=None):
       +        "Sign payment request with an OpenAlias"
       +        alias = self.config.get('alias')
       +        if not alias:
       +            raise Exception('No alias in your configuration')
       +        alias_addr = self.wallet.contacts.resolve(alias)['address']
       +        self.wallet.sign_payment_request(address, alias, alias_addr, password)
       +
       +    @command('w')
       +    def rmrequest(self, address):
       +        """Remove a payment request"""
       +        return self.wallet.remove_payment_request(address, self.config)
       +
       +    @command('w')
       +    def clearrequests(self):
       +        """Remove all payment requests"""
       +        for k in list(self.wallet.receive_requests.keys()):
       +            self.wallet.remove_payment_request(k, self.config)
       +
       +    @command('n')
       +    def notify(self, address, URL):
       +        """Watch an address. Every time the address changes, a http POST is sent to the URL."""
       +        def callback(x):
       +            import urllib.request
       +            headers = {'content-type':'application/json'}
       +            data = {'address':address, 'status':x.get('result')}
       +            serialized_data = util.to_bytes(json.dumps(data))
       +            try:
       +                req = urllib.request.Request(URL, serialized_data, headers)
       +                response_stream = urllib.request.urlopen(req, timeout=5)
       +                util.print_error('Got Response for %s' % address)
       +            except BaseException as e:
       +                util.print_error(str(e))
       +        self.network.subscribe_to_addresses([address], callback)
       +        return True
       +
       +    @command('wn')
       +    def is_synchronized(self):
       +        """ return wallet synchronization status """
       +        return self.wallet.is_up_to_date()
       +
       +    @command('n')
       +    def getfeerate(self, fee_method=None, fee_level=None):
       +        """Return current suggested fee rate (in sat/kvByte), according to config
       +        settings or supplied parameters.
       +        """
       +        if fee_method is None:
       +            dyn, mempool = None, None
       +        elif fee_method.lower() == 'static':
       +            dyn, mempool = False, False
       +        elif fee_method.lower() == 'eta':
       +            dyn, mempool = True, False
       +        elif fee_method.lower() == 'mempool':
       +            dyn, mempool = True, True
       +        else:
       +            raise Exception('Invalid fee estimation method: {}'.format(fee_method))
       +        if fee_level is not None:
       +            fee_level = Decimal(fee_level)
       +        return self.config.fee_per_kb(dyn=dyn, mempool=mempool, fee_level=fee_level)
       +
       +    @command('')
       +    def help(self):
       +        # for the python console
       +        return sorted(known_commands.keys())
       +
       +param_descriptions = {
       +    'privkey': 'Private key. Type \'?\' to get a prompt.',
       +    'destination': 'Bitcoin address, contact or alias',
       +    'address': 'Bitcoin address',
       +    'seed': 'Seed phrase',
       +    'txid': 'Transaction ID',
       +    'pos': 'Position',
       +    'height': 'Block height',
       +    'tx': 'Serialized transaction (hexadecimal)',
       +    'key': 'Variable name',
       +    'pubkey': 'Public key',
       +    'message': 'Clear text message. Use quotes if it contains spaces.',
       +    'encrypted': 'Encrypted message',
       +    'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.',
       +    'requested_amount': 'Requested amount (in BTC).',
       +    'outputs': 'list of ["address", amount]',
       +    'redeem_script': 'redeem script (hexadecimal)',
       +}
       +
       +command_options = {
       +    'password':    ("-W", "Password"),
       +    'new_password':(None, "New Password"),
       +    'receiving':   (None, "Show only receiving addresses"),
       +    'change':      (None, "Show only change addresses"),
       +    'frozen':      (None, "Show only frozen addresses"),
       +    'unused':      (None, "Show only unused addresses"),
       +    'funded':      (None, "Show only funded addresses"),
       +    'balance':     ("-b", "Show the balances of listed addresses"),
       +    'labels':      ("-l", "Show the labels of listed addresses"),
       +    'nocheck':     (None, "Do not verify aliases"),
       +    'imax':        (None, "Maximum number of inputs"),
       +    'fee':         ("-f", "Transaction fee (in BTC)"),
       +    'from_addr':   ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."),
       +    'change_addr': ("-c", "Change address. Default is a spare address, or the source address if it's not in the wallet"),
       +    'nbits':       (None, "Number of bits of entropy"),
       +    'segwit':      (None, "Create segwit seed"),
       +    'language':    ("-L", "Default language for wordlist"),
       +    'privkey':     (None, "Private key. Set to '?' to get a prompt."),
       +    'unsigned':    ("-u", "Do not sign transaction"),
       +    'rbf':         (None, "Replace-by-fee transaction"),
       +    'locktime':    (None, "Set locktime block number"),
       +    'domain':      ("-D", "List of addresses"),
       +    'memo':        ("-m", "Description of the request"),
       +    'expiration':  (None, "Time in seconds"),
       +    'timeout':     (None, "Timeout in seconds"),
       +    'force':       (None, "Create new address beyond gap limit, if no more addresses are available."),
       +    'pending':     (None, "Show only pending requests."),
       +    'expired':     (None, "Show only expired requests."),
       +    'paid':        (None, "Show only paid requests."),
       +    'show_addresses': (None, "Show input and output addresses"),
       +    'show_fiat':   (None, "Show fiat value of transactions"),
       +    'year':        (None, "Show history for a given year"),
       +    'fee_method':  (None, "Fee estimation method to use"),
       +    'fee_level':   (None, "Float between 0.0 and 1.0, representing fee slider position")
       +}
       +
       +
       +# don't use floats because of rounding errors
       +from .transaction import tx_from_str
       +json_loads = lambda x: json.loads(x, parse_float=lambda x: str(Decimal(x)))
       +arg_types = {
       +    'num': int,
       +    'nbits': int,
       +    'imax': int,
       +    'year': int,
       +    'tx': tx_from_str,
       +    'pubkeys': json_loads,
       +    'jsontx': json_loads,
       +    'inputs': json_loads,
       +    'outputs': json_loads,
       +    'fee': lambda x: str(Decimal(x)) if x is not None else None,
       +    'amount': lambda x: str(Decimal(x)) if x != '!' else '!',
       +    'locktime': int,
       +    'fee_method': str,
       +    'fee_level': json_loads,
       +}
       +
       +config_variables = {
       +
       +    'addrequest': {
       +        'requests_dir': 'directory where a bip70 file will be written.',
       +        'ssl_privkey': 'Path to your SSL private key, needed to sign the request.',
       +        'ssl_chain': 'Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end',
       +        'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
       +    },
       +    'listrequests':{
       +        'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
       +    }
       +}
       +
       +def set_default_subparser(self, name, args=None):
       +    """see http://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand"""
       +    subparser_found = False
       +    for arg in sys.argv[1:]:
       +        if arg in ['-h', '--help']:  # global help if no subparser
       +            break
       +    else:
       +        for x in self._subparsers._actions:
       +            if not isinstance(x, argparse._SubParsersAction):
       +                continue
       +            for sp_name in x._name_parser_map.keys():
       +                if sp_name in sys.argv[1:]:
       +                    subparser_found = True
       +        if not subparser_found:
       +            # insert default in first position, this implies no
       +            # global options without a sub_parsers specified
       +            if args is None:
       +                sys.argv.insert(1, name)
       +            else:
       +                args.insert(0, name)
       +
       +argparse.ArgumentParser.set_default_subparser = set_default_subparser
       +
       +
       +# workaround https://bugs.python.org/issue23058
       +# see https://github.com/nickstenning/honcho/pull/121
       +
       +def subparser_call(self, parser, namespace, values, option_string=None):
       +    from argparse import ArgumentError, SUPPRESS, _UNRECOGNIZED_ARGS_ATTR
       +    parser_name = values[0]
       +    arg_strings = values[1:]
       +    # set the parser name if requested
       +    if self.dest is not SUPPRESS:
       +        setattr(namespace, self.dest, parser_name)
       +    # select the parser
       +    try:
       +        parser = self._name_parser_map[parser_name]
       +    except KeyError:
       +        tup = parser_name, ', '.join(self._name_parser_map)
       +        msg = _('unknown parser {!r} (choices: {})').format(*tup)
       +        raise ArgumentError(self, msg)
       +    # parse all the remaining options into the namespace
       +    # store any unrecognized options on the object, so that the top
       +    # level parser can decide what to do with them
       +    namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
       +    if arg_strings:
       +        vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
       +        getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
       +
       +argparse._SubParsersAction.__call__ = subparser_call
       +
       +
       +def add_network_options(parser):
       +    parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=None, help="connect to one server only")
       +    parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
       +    parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
       +
       +def add_global_options(parser):
       +    group = parser.add_argument_group('global options')
       +    group.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Show debugging information")
       +    group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory")
       +    group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
       +    group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
       +    group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet")
       +    group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest")
       +    group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet")
       +
       +def get_parser():
       +    # create main parser
       +    parser = argparse.ArgumentParser(
       +        epilog="Run 'electrum help <command>' to see the help for a command")
       +    add_global_options(parser)
       +    subparsers = parser.add_subparsers(dest='cmd', metavar='<command>')
       +    # gui
       +    parser_gui = subparsers.add_parser('gui', description="Run Electrum's Graphical User Interface.", help="Run GUI (default)")
       +    parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)")
       +    parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'kivy', 'text', 'stdio'])
       +    parser_gui.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
       +    parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
       +    parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
       +    add_network_options(parser_gui)
       +    add_global_options(parser_gui)
       +    # daemon
       +    parser_daemon = subparsers.add_parser('daemon', help="Run Daemon")
       +    parser_daemon.add_argument("subcommand", choices=['start', 'status', 'stop', 'load_wallet', 'close_wallet'], nargs='?')
       +    #parser_daemon.set_defaults(func=run_daemon)
       +    add_network_options(parser_daemon)
       +    add_global_options(parser_daemon)
       +    # commands
       +    for cmdname in sorted(known_commands.keys()):
       +        cmd = known_commands[cmdname]
       +        p = subparsers.add_parser(cmdname, help=cmd.help, description=cmd.description)
       +        add_global_options(p)
       +        if cmdname == 'restore':
       +            p.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
       +        for optname, default in zip(cmd.options, cmd.defaults):
       +            a, help = command_options[optname]
       +            b = '--' + optname
       +            action = "store_true" if type(default) is bool else 'store'
       +            args = (a, b) if a else (b,)
       +            if action == 'store':
       +                _type = arg_types.get(optname, str)
       +                p.add_argument(*args, dest=optname, action=action, default=default, help=help, type=_type)
       +            else:
       +                p.add_argument(*args, dest=optname, action=action, default=default, help=help)
       +
       +        for param in cmd.params:
       +            h = param_descriptions.get(param, '')
       +            _type = arg_types.get(param, str)
       +            p.add_argument(param, help=h, type=_type)
       +
       +        cvh = config_variables.get(cmdname)
       +        if cvh:
       +            group = p.add_argument_group('configuration variables', '(set with setconfig/getconfig)')
       +            for k, v in cvh.items():
       +                group.add_argument(k, nargs='?', help=v)
       +
       +    # 'gui' is the default command
       +    parser.set_default_subparser('gui')
       +    return parser
   DIR diff --git a/lib/constants.py b/electrum/constants.py
   DIR diff --git a/lib/contacts.py b/electrum/contacts.py
   DIR diff --git a/lib/crypto.py b/electrum/crypto.py
   DIR diff --git a/lib/currencies.json b/electrum/currencies.json
   DIR diff --git a/electrum/daemon.py b/electrum/daemon.py
       t@@ -0,0 +1,316 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2015 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import ast
       +import os
       +import time
       +import traceback
       +import sys
       +
       +# from jsonrpc import JSONRPCResponseManager
       +import jsonrpclib
       +from .jsonrpc import VerifyingJSONRPCServer
       +
       +from .version import ELECTRUM_VERSION
       +from .network import Network
       +from .util import json_decode, DaemonThread
       +from .util import print_error, to_string
       +from .wallet import Wallet
       +from .storage import WalletStorage
       +from .commands import known_commands, Commands
       +from .simple_config import SimpleConfig
       +from .exchange_rate import FxThread
       +from .plugin import run_hook
       +
       +
       +def get_lockfile(config):
       +    return os.path.join(config.path, 'daemon')
       +
       +
       +def remove_lockfile(lockfile):
       +    os.unlink(lockfile)
       +
       +
       +def get_fd_or_server(config):
       +    '''Tries to create the lockfile, using O_EXCL to
       +    prevent races.  If it succeeds it returns the FD.
       +    Otherwise try and connect to the server specified in the lockfile.
       +    If this succeeds, the server is returned.  Otherwise remove the
       +    lockfile and try again.'''
       +    lockfile = get_lockfile(config)
       +    while True:
       +        try:
       +            return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644), None
       +        except OSError:
       +            pass
       +        server = get_server(config)
       +        if server is not None:
       +            return None, server
       +        # Couldn't connect; remove lockfile and try again.
       +        remove_lockfile(lockfile)
       +
       +
       +def get_server(config):
       +    lockfile = get_lockfile(config)
       +    while True:
       +        create_time = None
       +        try:
       +            with open(lockfile) as f:
       +                (host, port), create_time = ast.literal_eval(f.read())
       +                rpc_user, rpc_password = get_rpc_credentials(config)
       +                if rpc_password == '':
       +                    # authentication disabled
       +                    server_url = 'http://%s:%d' % (host, port)
       +                else:
       +                    server_url = 'http://%s:%s@%s:%d' % (
       +                        rpc_user, rpc_password, host, port)
       +                server = jsonrpclib.Server(server_url)
       +            # Test daemon is running
       +            server.ping()
       +            return server
       +        except Exception as e:
       +            print_error("[get_server]", e)
       +        if not create_time or create_time < time.time() - 1.0:
       +            return None
       +        # Sleep a bit and try again; it might have just been started
       +        time.sleep(1.0)
       +
       +
       +def get_rpc_credentials(config):
       +    rpc_user = config.get('rpcuser', None)
       +    rpc_password = config.get('rpcpassword', None)
       +    if rpc_user is None or rpc_password is None:
       +        rpc_user = 'user'
       +        import ecdsa, base64
       +        bits = 128
       +        nbytes = bits // 8 + (bits % 8 > 0)
       +        pw_int = ecdsa.util.randrange(pow(2, bits))
       +        pw_b64 = base64.b64encode(
       +            pw_int.to_bytes(nbytes, 'big'), b'-_')
       +        rpc_password = to_string(pw_b64, 'ascii')
       +        config.set_key('rpcuser', rpc_user)
       +        config.set_key('rpcpassword', rpc_password, save=True)
       +    elif rpc_password == '':
       +        from .util import print_stderr
       +        print_stderr('WARNING: RPC authentication is disabled.')
       +    return rpc_user, rpc_password
       +
       +
       +class Daemon(DaemonThread):
       +
       +    def __init__(self, config, fd, is_gui):
       +        DaemonThread.__init__(self)
       +        self.config = config
       +        if config.get('offline'):
       +            self.network = None
       +        else:
       +            self.network = Network(config)
       +            self.network.start()
       +        self.fx = FxThread(config, self.network)
       +        if self.network:
       +            self.network.add_jobs([self.fx])
       +        self.gui = None
       +        self.wallets = {}
       +        # Setup JSONRPC server
       +        self.init_server(config, fd, is_gui)
       +
       +    def init_server(self, config, fd, is_gui):
       +        host = config.get('rpchost', '127.0.0.1')
       +        port = config.get('rpcport', 0)
       +
       +        rpc_user, rpc_password = get_rpc_credentials(config)
       +        try:
       +            server = VerifyingJSONRPCServer((host, port), logRequests=False,
       +                                            rpc_user=rpc_user, rpc_password=rpc_password)
       +        except Exception as e:
       +            self.print_error('Warning: cannot initialize RPC server on host', host, e)
       +            self.server = None
       +            os.close(fd)
       +            return
       +        os.write(fd, bytes(repr((server.socket.getsockname(), time.time())), 'utf8'))
       +        os.close(fd)
       +        self.server = server
       +        server.timeout = 0.1
       +        server.register_function(self.ping, 'ping')
       +        if is_gui:
       +            server.register_function(self.run_gui, 'gui')
       +        else:
       +            server.register_function(self.run_daemon, 'daemon')
       +            self.cmd_runner = Commands(self.config, None, self.network)
       +            for cmdname in known_commands:
       +                server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
       +            server.register_function(self.run_cmdline, 'run_cmdline')
       +
       +    def ping(self):
       +        return True
       +
       +    def run_daemon(self, config_options):
       +        config = SimpleConfig(config_options)
       +        sub = config.get('subcommand')
       +        assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet']
       +        if sub in [None, 'start']:
       +            response = "Daemon already running"
       +        elif sub == 'load_wallet':
       +            path = config.get_wallet_path()
       +            wallet = self.load_wallet(path, config.get('password'))
       +            if wallet is not None:
       +                self.cmd_runner.wallet = wallet
       +                run_hook('load_wallet', wallet, None)
       +            response = wallet is not None
       +        elif sub == 'close_wallet':
       +            path = config.get_wallet_path()
       +            if path in self.wallets:
       +                self.stop_wallet(path)
       +                response = True
       +            else:
       +                response = False
       +        elif sub == 'status':
       +            if self.network:
       +                p = self.network.get_parameters()
       +                current_wallet = self.cmd_runner.wallet
       +                current_wallet_path = current_wallet.storage.path \
       +                                      if current_wallet else None
       +                response = {
       +                    'path': self.network.config.path,
       +                    'server': p[0],
       +                    'blockchain_height': self.network.get_local_height(),
       +                    'server_height': self.network.get_server_height(),
       +                    'spv_nodes': len(self.network.get_interfaces()),
       +                    'connected': self.network.is_connected(),
       +                    'auto_connect': p[4],
       +                    'version': ELECTRUM_VERSION,
       +                    'wallets': {k: w.is_up_to_date()
       +                                for k, w in self.wallets.items()},
       +                    'current_wallet': current_wallet_path,
       +                    'fee_per_kb': self.config.fee_per_kb(),
       +                }
       +            else:
       +                response = "Daemon offline"
       +        elif sub == 'stop':
       +            self.stop()
       +            response = "Daemon stopped"
       +        return response
       +
       +    def run_gui(self, config_options):
       +        config = SimpleConfig(config_options)
       +        if self.gui:
       +            #if hasattr(self.gui, 'new_window'):
       +            #    path = config.get_wallet_path()
       +            #    self.gui.new_window(path, config.get('url'))
       +            #    response = "ok"
       +            #else:
       +            #    response = "error: current GUI does not support multiple windows"
       +            response = "error: Electrum GUI already running"
       +        else:
       +            response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
       +        return response
       +
       +    def load_wallet(self, path, password):
       +        # wizard will be launched if we return
       +        if path in self.wallets:
       +            wallet = self.wallets[path]
       +            return wallet
       +        storage = WalletStorage(path, manual_upgrades=True)
       +        if not storage.file_exists():
       +            return
       +        if storage.is_encrypted():
       +            if not password:
       +                return
       +            storage.decrypt(password)
       +        if storage.requires_split():
       +            return
       +        if storage.get_action():
       +            return
       +        wallet = Wallet(storage)
       +        wallet.start_threads(self.network)
       +        self.wallets[path] = wallet
       +        return wallet
       +
       +    def add_wallet(self, wallet):
       +        path = wallet.storage.path
       +        self.wallets[path] = wallet
       +
       +    def get_wallet(self, path):
       +        return self.wallets.get(path)
       +
       +    def stop_wallet(self, path):
       +        wallet = self.wallets.pop(path)
       +        wallet.stop_threads()
       +
       +    def run_cmdline(self, config_options):
       +        password = config_options.get('password')
       +        new_password = config_options.get('new_password')
       +        config = SimpleConfig(config_options)
       +        # FIXME this is ugly...
       +        config.fee_estimates = self.network.config.fee_estimates.copy()
       +        config.mempool_fees  = self.network.config.mempool_fees.copy()
       +        cmdname = config.get('cmd')
       +        cmd = known_commands[cmdname]
       +        if cmd.requires_wallet:
       +            path = config.get_wallet_path()
       +            wallet = self.wallets.get(path)
       +            if wallet is None:
       +                return {'error': 'Wallet "%s" is not loaded. Use "electrum daemon load_wallet"'%os.path.basename(path) }
       +        else:
       +            wallet = None
       +        # arguments passed to function
       +        args = map(lambda x: config.get(x), cmd.params)
       +        # decode json arguments
       +        args = [json_decode(i) for i in args]
       +        # options
       +        kwargs = {}
       +        for x in cmd.options:
       +            kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))
       +        cmd_runner = Commands(config, wallet, self.network)
       +        func = getattr(cmd_runner, cmd.name)
       +        result = func(*args, **kwargs)
       +        return result
       +
       +    def run(self):
       +        while self.is_running():
       +            self.server.handle_request() if self.server else time.sleep(0.1)
       +        for k, wallet in self.wallets.items():
       +            wallet.stop_threads()
       +        if self.network:
       +            self.print_error("shutting down network")
       +            self.network.stop()
       +            self.network.join()
       +        self.on_stop()
       +
       +    def stop(self):
       +        self.print_error("stopping, removing lockfile")
       +        remove_lockfile(get_lockfile(self.config))
       +        DaemonThread.stop(self)
       +
       +    def init_gui(self, config, plugins):
       +        gui_name = config.get('gui', 'qt')
       +        if gui_name in ['lite', 'classic']:
       +            gui_name = 'qt'
       +        gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
       +        self.gui = gui.ElectrumGui(config, self, plugins)
       +        try:
       +            self.gui.main()
       +        except BaseException as e:
       +            traceback.print_exc(file=sys.stdout)
       +            # app will exit now
   DIR diff --git a/lib/dnssec.py b/electrum/dnssec.py
   DIR diff --git a/lib/ecc.py b/electrum/ecc.py
   DIR diff --git a/lib/ecc_fast.py b/electrum/ecc_fast.py
   DIR diff --git a/electrum/electrum b/electrum/electrum
       t@@ -0,0 +1 @@
       +../run_electrum
       +\ No newline at end of file
   DIR diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py
       t@@ -0,0 +1,573 @@
       +from datetime import datetime
       +import inspect
       +import requests
       +import sys
       +import os
       +import json
       +from threading import Thread
       +import time
       +import csv
       +import decimal
       +from decimal import Decimal
       +
       +from .bitcoin import COIN
       +from .i18n import _
       +from .util import PrintError, ThreadJob, make_dir
       +
       +
       +# See https://en.wikipedia.org/wiki/ISO_4217
       +CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,
       +                  'CVE': 0, 'DJF': 0, 'GNF': 0, 'IQD': 3, 'ISK': 0,
       +                  'JOD': 3, 'JPY': 0, 'KMF': 0, 'KRW': 0, 'KWD': 3,
       +                  'LYD': 3, 'MGA': 1, 'MRO': 1, 'OMR': 3, 'PYG': 0,
       +                  'RWF': 0, 'TND': 3, 'UGX': 0, 'UYI': 0, 'VND': 0,
       +                  'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0}
       +
       +
       +class ExchangeBase(PrintError):
       +
       +    def __init__(self, on_quotes, on_history):
       +        self.history = {}
       +        self.quotes = {}
       +        self.on_quotes = on_quotes
       +        self.on_history = on_history
       +
       +    def get_json(self, site, get_string):
       +        # APIs must have https
       +        url = ''.join(['https://', site, get_string])
       +        response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'}, timeout=10)
       +        return response.json()
       +
       +    def get_csv(self, site, get_string):
       +        url = ''.join(['https://', site, get_string])
       +        response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'})
       +        reader = csv.DictReader(response.content.decode().split('\n'))
       +        return list(reader)
       +
       +    def name(self):
       +        return self.__class__.__name__
       +
       +    def update_safe(self, ccy):
       +        try:
       +            self.print_error("getting fx quotes for", ccy)
       +            self.quotes = self.get_rates(ccy)
       +            self.print_error("received fx quotes")
       +        except BaseException as e:
       +            self.print_error("failed fx quotes:", e)
       +        self.on_quotes()
       +
       +    def update(self, ccy):
       +        t = Thread(target=self.update_safe, args=(ccy,))
       +        t.setDaemon(True)
       +        t.start()
       +
       +    def read_historical_rates(self, ccy, cache_dir):
       +        filename = os.path.join(cache_dir, self.name() + '_'+ ccy)
       +        if os.path.exists(filename):
       +            timestamp = os.stat(filename).st_mtime
       +            try:
       +                with open(filename, 'r', encoding='utf-8') as f:
       +                    h = json.loads(f.read())
       +                h['timestamp'] = timestamp
       +            except:
       +                h = None
       +        else:
       +            h = None
       +        if h:
       +            self.history[ccy] = h
       +            self.on_history()
       +        return h
       +
       +    def get_historical_rates_safe(self, ccy, cache_dir):
       +        try:
       +            self.print_error("requesting fx history for", ccy)
       +            h = self.request_history(ccy)
       +            self.print_error("received fx history for", ccy)
       +        except BaseException as e:
       +            self.print_error("failed fx history:", e)
       +            return
       +        filename = os.path.join(cache_dir, self.name() + '_' + ccy)
       +        with open(filename, 'w', encoding='utf-8') as f:
       +            f.write(json.dumps(h))
       +        h['timestamp'] = time.time()
       +        self.history[ccy] = h
       +        self.on_history()
       +
       +    def get_historical_rates(self, ccy, cache_dir):
       +        if ccy not in self.history_ccys():
       +            return
       +        h = self.history.get(ccy)
       +        if h is None:
       +            h = self.read_historical_rates(ccy, cache_dir)
       +        if h is None or h['timestamp'] < time.time() - 24*3600:
       +            t = Thread(target=self.get_historical_rates_safe, args=(ccy, cache_dir))
       +            t.setDaemon(True)
       +            t.start()
       +
       +    def history_ccys(self):
       +        return []
       +
       +    def historical_rate(self, ccy, d_t):
       +        return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d'), 'NaN')
       +
       +    def get_currencies(self):
       +        rates = self.get_rates('')
       +        return sorted([str(a) for (a, b) in rates.items() if b is not None and len(a)==3])
       +
       +class BitcoinAverage(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('apiv2.bitcoinaverage.com', '/indices/global/ticker/short')
       +        return dict([(r.replace("BTC", ""), Decimal(json[r]['last']))
       +                     for r in json if r != 'timestamp'])
       +
       +    def history_ccys(self):
       +        return ['AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'EUR', 'GBP', 'IDR', 'ILS',
       +                'MXN', 'NOK', 'NZD', 'PLN', 'RON', 'RUB', 'SEK', 'SGD', 'USD',
       +                'ZAR']
       +
       +    def request_history(self, ccy):
       +        history = self.get_csv('apiv2.bitcoinaverage.com',
       +                               "/indices/global/history/BTC%s?period=alltime&format=csv" % ccy)
       +        return dict([(h['DateTime'][:10], h['Average'])
       +                     for h in history])
       +
       +
       +class Bitcointoyou(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('bitcointoyou.com', "/API/ticker.aspx")
       +        return {'BRL': Decimal(json['ticker']['last'])}
       +
       +    def history_ccys(self):
       +        return ['BRL']
       +
       +
       +class BitcoinVenezuela(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('api.bitcoinvenezuela.com', '/')
       +        rates = [(r, json['BTC'][r]) for r in json['BTC']
       +                 if json['BTC'][r] is not None]  # Giving NULL for LTC
       +        return dict(rates)
       +
       +    def history_ccys(self):
       +        return ['ARS', 'EUR', 'USD', 'VEF']
       +
       +    def request_history(self, ccy):
       +        return self.get_json('api.bitcoinvenezuela.com',
       +                             "/historical/index.php?coin=BTC")[ccy +'_BTC']
       +
       +
       +class Bitbank(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('public.bitbank.cc', '/btc_jpy/ticker')
       +        return {'JPY': Decimal(json['data']['last'])}
       +
       +
       +class BitFlyer(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('bitflyer.jp', '/api/echo/price')
       +        return {'JPY': Decimal(json['mid'])}
       +
       +
       +class Bitmarket(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('www.bitmarket.pl', '/json/BTCPLN/ticker.json')
       +        return {'PLN': Decimal(json['last'])}
       +
       +
       +class BitPay(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('bitpay.com', '/api/rates')
       +        return dict([(r['code'], Decimal(r['rate'])) for r in json])
       +
       +
       +class Bitso(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('api.bitso.com', '/v2/ticker')
       +        return {'MXN': Decimal(json['last'])}
       +
       +
       +class BitStamp(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('www.bitstamp.net', '/api/ticker/')
       +        return {'USD': Decimal(json['last'])}
       +
       +
       +class Bitvalor(ExchangeBase):
       +
       +    def get_rates(self,ccy):
       +        json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
       +        return {'BRL': Decimal(json['ticker_1h']['total']['last'])}
       +
       +
       +class BlockchainInfo(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('blockchain.info', '/ticker')
       +        return dict([(r, Decimal(json[r]['15m'])) for r in json])
       +
       +
       +class BTCChina(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('data.btcchina.com', '/data/ticker')
       +        return {'CNY': Decimal(json['ticker']['last'])}
       +
       +
       +class BTCParalelo(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('btcparalelo.com', '/api/price')
       +        return {'VEF': Decimal(json['price'])}
       +
       +
       +class Coinbase(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('coinbase.com',
       +                             '/api/v1/currencies/exchange_rates')
       +        return dict([(r[7:].upper(), Decimal(json[r]))
       +                     for r in json if r.startswith('btc_to_')])
       +
       +
       +class CoinDesk(ExchangeBase):
       +
       +    def get_currencies(self):
       +        dicts = self.get_json('api.coindesk.com',
       +                              '/v1/bpi/supported-currencies.json')
       +        return [d['currency'] for d in dicts]
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('api.coindesk.com',
       +                             '/v1/bpi/currentprice/%s.json' % ccy)
       +        result = {ccy: Decimal(json['bpi'][ccy]['rate_float'])}
       +        return result
       +
       +    def history_starts(self):
       +        return { 'USD': '2012-11-30', 'EUR': '2013-09-01' }
       +
       +    def history_ccys(self):
       +        return self.history_starts().keys()
       +
       +    def request_history(self, ccy):
       +        start = self.history_starts()[ccy]
       +        end = datetime.today().strftime('%Y-%m-%d')
       +        # Note ?currency and ?index don't work as documented.  Sigh.
       +        query = ('/v1/bpi/historical/close.json?start=%s&end=%s'
       +                 % (start, end))
       +        json = self.get_json('api.coindesk.com', query)
       +        return json['bpi']
       +
       +
       +class Coinsecure(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('api.coinsecure.in', '/v0/noauth/newticker')
       +        return {'INR': Decimal(json['lastprice'] / 100.0 )}
       +
       +
       +class Foxbit(ExchangeBase):
       +
       +    def get_rates(self,ccy):
       +        json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
       +        return {'BRL': Decimal(json['ticker_1h']['exchanges']['FOX']['last'])}
       +
       +
       +class itBit(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        ccys = ['USD', 'EUR', 'SGD']
       +        json = self.get_json('api.itbit.com', '/v1/markets/XBT%s/ticker' % ccy)
       +        result = dict.fromkeys(ccys)
       +        if ccy in ccys:
       +            result[ccy] = Decimal(json['lastPrice'])
       +        return result
       +
       +
       +class Kraken(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        ccys = ['EUR', 'USD', 'CAD', 'GBP', 'JPY']
       +        pairs = ['XBT%s' % c for c in ccys]
       +        json = self.get_json('api.kraken.com',
       +                             '/0/public/Ticker?pair=%s' % ','.join(pairs))
       +        return dict((k[-3:], Decimal(float(v['c'][0])))
       +                     for k, v in json['result'].items())
       +
       +
       +class LocalBitcoins(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('localbitcoins.com',
       +                             '/bitcoinaverage/ticker-all-currencies/')
       +        return dict([(r, Decimal(json[r]['rates']['last'])) for r in json])
       +
       +
       +class MercadoBitcoin(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
       +        return {'BRL': Decimal(json['ticker_1h']['exchanges']['MBT']['last'])}
       +
       +
       +class NegocieCoins(ExchangeBase):
       +
       +    def get_rates(self,ccy):
       +        json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
       +        return {'BRL': Decimal(json['ticker_1h']['exchanges']['NEG']['last'])}
       +
       +class TheRockTrading(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('api.therocktrading.com', 
       +                             '/v1/funds/BTCEUR/ticker')
       +        return {'EUR': Decimal(json['last'])}
       +
       +class Unocoin(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('www.unocoin.com', 'trade?buy')
       +        return {'INR': Decimal(json)}
       +
       +
       +class WEX(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json_eur = self.get_json('wex.nz', '/api/3/ticker/btc_eur')
       +        json_rub = self.get_json('wex.nz', '/api/3/ticker/btc_rur')
       +        json_usd = self.get_json('wex.nz', '/api/3/ticker/btc_usd')
       +        return {'EUR': Decimal(json_eur['btc_eur']['last']),
       +                'RUB': Decimal(json_rub['btc_rur']['last']),
       +                'USD': Decimal(json_usd['btc_usd']['last'])}
       +
       +
       +class Winkdex(ExchangeBase):
       +
       +    def get_rates(self, ccy):
       +        json = self.get_json('winkdex.com', '/api/v0/price')
       +        return {'USD': Decimal(json['price'] / 100.0)}
       +
       +    def history_ccys(self):
       +        return ['USD']
       +
       +    def request_history(self, ccy):
       +        json = self.get_json('winkdex.com',
       +                             "/api/v0/series?start_time=1342915200")
       +        history = json['series'][0]['results']
       +        return dict([(h['timestamp'][:10], h['price'] / 100.0)
       +                     for h in history])
       +
       +
       +class Zaif(ExchangeBase):
       +    def get_rates(self, ccy):
       +        json = self.get_json('api.zaif.jp', '/api/1/last_price/btc_jpy')
       +        return {'JPY': Decimal(json['last_price'])}
       +
       +
       +def dictinvert(d):
       +    inv = {}
       +    for k, vlist in d.items():
       +        for v in vlist:
       +            keys = inv.setdefault(v, [])
       +            keys.append(k)
       +    return inv
       +
       +def get_exchanges_and_currencies():
       +    import os, json
       +    path = os.path.join(os.path.dirname(__file__), 'currencies.json')
       +    try:
       +        with open(path, 'r', encoding='utf-8') as f:
       +            return json.loads(f.read())
       +    except:
       +        pass
       +    d = {}
       +    is_exchange = lambda obj: (inspect.isclass(obj)
       +                               and issubclass(obj, ExchangeBase)
       +                               and obj != ExchangeBase)
       +    exchanges = dict(inspect.getmembers(sys.modules[__name__], is_exchange))
       +    for name, klass in exchanges.items():
       +        exchange = klass(None, None)
       +        try:
       +            d[name] = exchange.get_currencies()
       +            print(name, "ok")
       +        except:
       +            print(name, "error")
       +            continue
       +    with open(path, 'w', encoding='utf-8') as f:
       +        f.write(json.dumps(d, indent=4, sort_keys=True))
       +    return d
       +
       +
       +CURRENCIES = get_exchanges_and_currencies()
       +
       +
       +def get_exchanges_by_ccy(history=True):
       +    if not history:
       +        return dictinvert(CURRENCIES)
       +    d = {}
       +    exchanges = CURRENCIES.keys()
       +    for name in exchanges:
       +        klass = globals()[name]
       +        exchange = klass(None, None)
       +        d[name] = exchange.history_ccys()
       +    return dictinvert(d)
       +
       +
       +class FxThread(ThreadJob):
       +
       +    def __init__(self, config, network):
       +        self.config = config
       +        self.network = network
       +        self.ccy = self.get_currency()
       +        self.history_used_spot = False
       +        self.ccy_combo = None
       +        self.hist_checkbox = None
       +        self.cache_dir = os.path.join(config.path, 'cache')
       +        self.set_exchange(self.config_exchange())
       +        make_dir(self.cache_dir)
       +
       +    def get_currencies(self, h):
       +        d = get_exchanges_by_ccy(h)
       +        return sorted(d.keys())
       +
       +    def get_exchanges_by_ccy(self, ccy, h):
       +        d = get_exchanges_by_ccy(h)
       +        return d.get(ccy, [])
       +
       +    def ccy_amount_str(self, amount, commas):
       +        prec = CCY_PRECISIONS.get(self.ccy, 2)
       +        fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
       +        try:
       +            rounded_amount = round(amount, prec)
       +        except decimal.InvalidOperation:
       +            rounded_amount = amount
       +        return fmt_str.format(rounded_amount)
       +
       +    def run(self):
       +        # This runs from the plugins thread which catches exceptions
       +        if self.is_enabled():
       +            if self.timeout ==0 and self.show_history():
       +                self.exchange.get_historical_rates(self.ccy, self.cache_dir)
       +            if self.timeout <= time.time():
       +                self.timeout = time.time() + 150
       +                self.exchange.update(self.ccy)
       +
       +    def is_enabled(self):
       +        return bool(self.config.get('use_exchange_rate'))
       +
       +    def set_enabled(self, b):
       +        return self.config.set_key('use_exchange_rate', bool(b))
       +
       +    def get_history_config(self):
       +        return bool(self.config.get('history_rates'))
       +
       +    def set_history_config(self, b):
       +        self.config.set_key('history_rates', bool(b))
       +
       +    def get_history_capital_gains_config(self):
       +        return bool(self.config.get('history_rates_capital_gains', False))
       +
       +    def set_history_capital_gains_config(self, b):
       +        self.config.set_key('history_rates_capital_gains', bool(b))
       +
       +    def get_fiat_address_config(self):
       +        return bool(self.config.get('fiat_address'))
       +
       +    def set_fiat_address_config(self, b):
       +        self.config.set_key('fiat_address', bool(b))
       +
       +    def get_currency(self):
       +        '''Use when dynamic fetching is needed'''
       +        return self.config.get("currency", "EUR")
       +
       +    def config_exchange(self):
       +        return self.config.get('use_exchange', 'BitcoinAverage')
       +
       +    def show_history(self):
       +        return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()
       +
       +    def set_currency(self, ccy):
       +        self.ccy = ccy
       +        self.config.set_key('currency', ccy, True)
       +        self.timeout = 0 # Because self.ccy changes
       +        self.on_quotes()
       +
       +    def set_exchange(self, name):
       +        class_ = globals().get(name, BitcoinAverage)
       +        self.print_error("using exchange", name)
       +        if self.config_exchange() != name:
       +            self.config.set_key('use_exchange', name, True)
       +        self.exchange = class_(self.on_quotes, self.on_history)
       +        # A new exchange means new fx quotes, initially empty.  Force
       +        # a quote refresh
       +        self.timeout = 0
       +        self.exchange.read_historical_rates(self.ccy, self.cache_dir)
       +
       +    def on_quotes(self):
       +        if self.network:
       +            self.network.trigger_callback('on_quotes')
       +
       +    def on_history(self):
       +        if self.network:
       +            self.network.trigger_callback('on_history')
       +
       +    def exchange_rate(self):
       +        '''Returns None, or the exchange rate as a Decimal'''
       +        rate = self.exchange.quotes.get(self.ccy)
       +        if rate is None:
       +            return Decimal('NaN')
       +        return Decimal(rate)
       +
       +    def format_amount(self, btc_balance):
       +        rate = self.exchange_rate()
       +        return '' if rate.is_nan() else "%s" % self.value_str(btc_balance, rate)
       +
       +    def format_amount_and_units(self, btc_balance):
       +        rate = self.exchange_rate()
       +        return '' if rate.is_nan() else "%s %s" % (self.value_str(btc_balance, rate), self.ccy)
       +
       +    def get_fiat_status_text(self, btc_balance, base_unit, decimal_point):
       +        rate = self.exchange_rate()
       +        return _("  (No FX rate available)") if rate.is_nan() else " 1 %s~%s %s" % (base_unit,
       +            self.value_str(COIN / (10**(8 - decimal_point)), rate), self.ccy)
       +
       +    def fiat_value(self, satoshis, rate):
       +        return Decimal('NaN') if satoshis is None else Decimal(satoshis) / COIN * Decimal(rate)
       +
       +    def value_str(self, satoshis, rate):
       +        return self.format_fiat(self.fiat_value(satoshis, rate))
       +
       +    def format_fiat(self, value):
       +        if value.is_nan():
       +            return _("No data")
       +        return "%s" % (self.ccy_amount_str(value, True))
       +
       +    def history_rate(self, d_t):
       +        if d_t is None:
       +            return Decimal('NaN')
       +        rate = self.exchange.historical_rate(self.ccy, d_t)
       +        # Frequently there is no rate for today, until tomorrow :)
       +        # Use spot quotes in that case
       +        if rate == 'NaN' and (datetime.today().date() - d_t.date()).days <= 2:
       +            rate = self.exchange.quotes.get(self.ccy, 'NaN')
       +            self.history_used_spot = True
       +        return Decimal(rate)
       +
       +    def historical_value_str(self, satoshis, d_t):
       +        return self.format_fiat(self.historical_value(satoshis, d_t))
       +
       +    def historical_value(self, satoshis, d_t):
       +        return self.fiat_value(satoshis, self.history_rate(d_t))
       +
       +    def timestamp_rate(self, timestamp):
       +        from .util import timestamp_to_datetime
       +        date = timestamp_to_datetime(timestamp)
       +        return self.history_rate(date)
   DIR diff --git a/gui/__init__.py b/electrum/gui/__init__.py
   DIR diff --git a/electrum/gui/kivy/Makefile b/electrum/gui/kivy/Makefile
       t@@ -0,0 +1,32 @@
       +PYTHON = python3
       +
       +# needs kivy installed or in PYTHONPATH
       +
       +.PHONY: theming apk clean
       +
       +theming:
       +        $(PYTHON) -m kivy.atlas theming/light 1024 theming/light/*.png
       +prepare:
       +        # running pre build setup
       +        @cp tools/buildozer.spec ../../buildozer.spec
       +        # copy electrum to main.py
       +        @cp ../../../run_electrum ../../main.py
       +        @-if [ ! -d "../../.buildozer" ];then \
       +                cd ../..; buildozer android debug;\
       +                cp -f electrum/gui/kivy/tools/blacklist.txt .buildozer/android/platform/python-for-android/src/blacklist.txt;\
       +                rm -rf ./.buildozer/android/platform/python-for-android/dist;\
       +        fi
       +apk:
       +        @make prepare
       +        @-cd ../..; buildozer android debug deploy run
       +        @make clean
       +release:
       +        @make prepare
       +        @-cd ../..; buildozer android release
       +        @make clean
       +clean:
       +        # Cleaning up
       +        # rename main.py to electrum
       +        @-rm ../../main.py
       +        # remove buildozer.spec
       +        @-rm ../../buildozer.spec
   DIR diff --git a/gui/kivy/Readme.md b/electrum/gui/kivy/Readme.md
   DIR diff --git a/gui/kivy/__init__.py b/electrum/gui/kivy/__init__.py
   DIR diff --git a/gui/kivy/data/background.png b/electrum/gui/kivy/data/background.png
       Binary files differ.
   DIR diff --git a/gui/kivy/data/fonts/Roboto-Bold.ttf b/electrum/gui/kivy/data/fonts/Roboto-Bold.ttf
       Binary files differ.
   DIR diff --git a/gui/kivy/data/fonts/Roboto-Condensed.ttf b/electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf
       Binary files differ.
   DIR diff --git a/gui/kivy/data/fonts/Roboto-Medium.ttf b/electrum/gui/kivy/data/fonts/Roboto-Medium.ttf
       Binary files differ.
   DIR diff --git a/gui/kivy/data/fonts/Roboto.ttf b/electrum/gui/kivy/data/fonts/Roboto.ttf
       Binary files differ.
   DIR diff --git a/gui/kivy/data/fonts/tron/License.txt b/electrum/gui/kivy/data/fonts/tron/License.txt
   DIR diff --git a/gui/kivy/data/fonts/tron/Readme.txt b/electrum/gui/kivy/data/fonts/tron/Readme.txt
   DIR diff --git a/gui/kivy/data/fonts/tron/Tr2n.ttf b/electrum/gui/kivy/data/fonts/tron/Tr2n.ttf
       Binary files differ.
   DIR diff --git a/gui/kivy/data/glsl/default.fs b/electrum/gui/kivy/data/glsl/default.fs
   DIR diff --git a/gui/kivy/data/glsl/default.png b/electrum/gui/kivy/data/glsl/default.png
       Binary files differ.
   DIR diff --git a/gui/kivy/data/glsl/default.vs b/electrum/gui/kivy/data/glsl/default.vs
   DIR diff --git a/gui/kivy/data/glsl/header.fs b/electrum/gui/kivy/data/glsl/header.fs
   DIR diff --git a/gui/kivy/data/glsl/header.vs b/electrum/gui/kivy/data/glsl/header.vs
   DIR diff --git a/gui/kivy/data/images/defaulttheme-0.png b/electrum/gui/kivy/data/images/defaulttheme-0.png
       Binary files differ.
   DIR diff --git a/gui/kivy/data/images/defaulttheme.atlas b/electrum/gui/kivy/data/images/defaulttheme.atlas
   DIR diff --git a/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java b/electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java
   DIR diff --git a/gui/kivy/data/logo/kivy-icon-32.png b/electrum/gui/kivy/data/logo/kivy-icon-32.png
       Binary files differ.
   DIR diff --git a/gui/kivy/data/style.kv b/electrum/gui/kivy/data/style.kv
   DIR diff --git a/gui/kivy/i18n.py b/electrum/gui/kivy/i18n.py
   DIR diff --git a/electrum/gui/kivy/main.kv b/electrum/gui/kivy/main.kv
       t@@ -0,0 +1,464 @@
       +#:import Clock kivy.clock.Clock
       +#:import Window kivy.core.window.Window
       +#:import Factory kivy.factory.Factory
       +#:import _ electrum.gui.kivy.i18n._
       +
       +
       +###########################
       +#     Global Defaults
       +###########################
       +
       +<Label>
       +    markup: True
       +    font_name: 'Roboto'
       +    font_size: '16sp'
       +    bound: False
       +    on_text: if isinstance(self.text, _) and not self.bound: self.bound=True; _.bind(self)
       +
       +<TextInput>
       +    on_focus: app._focused_widget = root
       +    font_size: '18sp'
       +
       +<Button>
       +    on_parent: self.MIN_STATE_TIME = 0.1
       +
       +<ListItemButton>
       +    font_size: '12sp'
       +
       +<Carousel>:
       +    canvas.before:
       +        Color:
       +            rgba: 0.1, 0.1, 0.1, 1
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +
       +<ActionView>:
       +    canvas.before:
       +        Color:
       +            rgba: 0.1, 0.1, 0.1, 1
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +
       +
       +# Custom Global Widgets
       +
       +<TopLabel>
       +    size_hint_y: None
       +    text_size: self.width, None
       +    height: self.texture_size[1]
       +
       +<VGridLayout@GridLayout>:
       +    rows: 1
       +    size_hint: 1, None
       +    height: self.minimum_height
       +
       +
       +
       +<IconButton@Button>:
       +    icon: ''
       +    AnchorLayout:
       +        pos: self.parent.pos
       +        size: self.parent.size
       +        orientation: 'lr-tb'
       +        Image:
       +            source: self.parent.parent.icon
       +            size_hint_x: None
       +            size: '30dp', '30dp'
       +
       +
       +
       +#########################
       +#       Dialogs
       +#########################
       +<BoxLabel@BoxLayout>
       +    text: ''
       +    value: ''
       +    size_hint_y: None
       +    height: max(lbl1.height, lbl2.height)
       +    TopLabel
       +        id: lbl1
       +        text: root.text
       +        pos_hint: {'top':1}
       +    TopLabel
       +        id: lbl2
       +        text: root.value
       +
       +<OutputItem>
       +    address: ''
       +    value: ''
       +    size_hint_y: None
       +    height: max(lbl1.height, lbl2.height)
       +    TopLabel
       +        id: lbl1
       +        text: '[ref=%s]%s[/ref]'%(root.address, root.address)
       +        font_size: '6pt'
       +        shorten: True
       +        size_hint_x: 0.65
       +        on_ref_press:
       +            app._clipboard.copy(root.address)
       +            app.show_info(_('Address copied to clipboard') + ' ' + root.address)
       +    TopLabel
       +        id: lbl2
       +        text: root.value
       +        font_size: '6pt'
       +        size_hint_x: 0.35
       +        halign: 'right'
       +
       +
       +<OutputList>
       +    viewclass: 'OutputItem'
       +    size_hint: 1, None
       +    height: min(output_list_layout.minimum_height, dp(144))
       +    scroll_type: ['bars', 'content']
       +    bar_width: dp(15)
       +    RecycleBoxLayout:
       +        orientation: 'vertical'
       +        default_size: None, pt(6)
       +        default_size_hint: 1, None
       +        size_hint: 1, None
       +        height: self.minimum_height
       +        id: output_list_layout
       +        spacing: '10dp'
       +        padding: '10dp'
       +        canvas.before:
       +            Color:
       +                rgb: .3, .3, .3
       +            Rectangle:
       +                size: self.size
       +                pos: self.pos
       +
       +<RefLabel>
       +    font_size: '6pt'
       +    name: ''
       +    data: ''
       +    text: self.data
       +    touched: False
       +    padding: '10dp', '10dp'
       +    on_touch_down:
       +        touch = args[1]
       +        if self.collide_point(*touch.pos): app.on_ref_label(self, touch)
       +        else: self.touched = False
       +    canvas.before:
       +        Color:
       +            rgb: .3, .3, .3
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +
       +<TxHashLabel@RefLabel>
       +    data: ''
       +    text: ' '.join(map(''.join, zip(*[iter(self.data)]*4))) if self.data else ''
       +
       +<InfoBubble>
       +    size_hint: None, None
       +    width: '270dp' if root.fs else min(self.width, dp(270))
       +    height: self.width if self.fs else (lbl.texture_size[1] + dp(27))
       +    BoxLayout:
       +        padding: '5dp' if root.fs else 0
       +        Widget:
       +            size_hint: None, 1
       +            width: '4dp' if root.fs else '2dp'
       +        Image:
       +            id: img
       +            source: root.icon
       +            mipmap: True
       +            size_hint: None, 1
       +            width: (root.width - dp(20)) if root.fs  else (0 if not root.icon else '32dp')
       +        Widget:
       +            size_hint_x: None
       +            width: '5dp'
       +        Label:
       +            id: lbl
       +            markup: True
       +            font_size: '12sp'
       +            text: root.message
       +            text_size: self.width, None
       +            valign: 'middle'
       +            size_hint: 1, 1
       +            width: 0 if root.fs else (root.width - img.width)
       +
       +
       +<SendReceiveBlueBottom@GridLayout>
       +    item_height: dp(42)
       +    foreground_color: .843, .914, .972, 1
       +    cols: 1
       +    padding: '12dp', 0
       +    canvas.before:
       +        Color:
       +            rgba: 0.192, .498, 0.745, 1
       +        BorderImage:
       +            source: 'atlas://electrum/gui/kivy/theming/light/card_bottom'
       +            size: self.size
       +            pos: self.pos
       +
       +
       +<AddressFilter@GridLayout>
       +        item_height: dp(42)
       +        item_width: dp(60)
       +        foreground_color: .843, .914, .972, 1
       +        cols: 1
       +        canvas.before:
       +                Color:
       +                        rgba: 0.192, .498, 0.745, 1
       +                BorderImage:
       +                        source: 'atlas://electrum/gui/kivy/theming/light/card_bottom'
       +                        size: self.size
       +                        pos: self.pos
       +
       +<SearchBox@GridLayout>
       +        item_height: dp(42)
       +        foreground_color: .843, .914, .972, 1
       +        cols: 1
       +        padding: '12dp', 0
       +        canvas.before:
       +                Color:
       +                        rgba: 0.192, .498, 0.745, 1
       +        BorderImage:
       +            source: 'atlas://electrum/gui/kivy/theming/light/card_bottom'
       +            size: self.size
       +            pos: self.pos
       +
       +<CardSeparator@Widget>
       +    size_hint: 1, None
       +    height: dp(1)
       +    color: .909, .909, .909, 1
       +    canvas:
       +        Color:
       +            rgba: root.color if root.color else (0, 0, 0, 0)
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +
       +<CardItem@ToggleButtonBehavior+BoxLayout>
       +    size_hint: 1, None
       +    height: '65dp'
       +    group: 'requests'
       +    padding: dp(12)
       +    spacing: dp(5)
       +    screen: None
       +    on_release:
       +        self.screen.show_menu(args[0]) if self.state == 'down' else self.screen.hide_menu()
       +    canvas.before:
       +        Color:
       +            rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.15, 0.15, 0.17, 1)
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +
       +<BlueButton@Button>:
       +    background_color: 1, .585, .878, 0
       +    halign: 'left'
       +    text_size: (self.width-10, None)
       +    size_hint: 0.5, None
       +    default_text: ''
       +    text: self.default_text
       +    padding: '5dp', '5dp'
       +    height: '40dp'
       +    text_color: self.foreground_color
       +    disabled_color: 1, 1, 1, 1
       +    foreground_color: 1, 1, 1, 1
       +    canvas.before:
       +        Color:
       +            rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +
       +<AddressButton@Button>:
       +        background_color: 1, .585, .878, 0
       +        halign: 'center'
       +        text_size: (self.width, None)
       +        shorten: True
       +        size_hint: 0.5, None
       +        default_text: ''
       +        text: self.default_text
       +        padding: '5dp', '5dp'
       +        height: '40dp'
       +        text_color: self.foreground_color
       +        disabled_color: 1, 1, 1, 1
       +        foreground_color: 1, 1, 1, 1
       +        canvas.before:
       +                Color:
       +                        rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
       +                Rectangle:
       +                        size: self.size
       +                        pos: self.pos
       +
       +<KButton@Button>:
       +    size_hint: 1, None
       +    height: '60dp'
       +    font_size: '30dp'
       +    on_release:
       +        self.parent.update_amount(self.text)
       +
       +
       +<StripLayout>
       +    padding: 0, 0, 0, 0
       +
       +<TabbedPanelStrip>:
       +    on_parent:
       +        if self.parent: self.parent.bar_width = 0
       +        if self.parent: self.parent.scroll_x = 0.5
       +
       +
       +<TabbedCarousel>
       +    carousel: carousel
       +    do_default_tab: False
       +    Carousel:
       +        scroll_timeout: 250
       +        scroll_distance: '100dp'
       +        anim_type: 'out_quart'
       +        min_move: .05
       +        anim_move_duration: .1
       +        anim_cancel_duration: .54
       +        on_index: root.on_index(*args)
       +        id: carousel
       +
       +
       +
       +<CleanHeader@TabbedPanelHeader>
       +    border: 16, 0, 16, 0
       +    markup: False
       +    text_size: self.size
       +    halign: 'center'
       +    valign: 'middle'
       +    bold: True
       +    font_size: '12.5sp'
       +    background_normal: 'atlas://electrum/gui/kivy/theming/light/tab_btn'
       +    background_down: 'atlas://electrum/gui/kivy/theming/light/tab_btn_pressed'
       +
       +
       +<ColoredLabel@Label>:
       +    font_size: '48sp'
       +    color: (.6, .6, .6, 1)
       +    canvas.before:
       +        Color:
       +            rgb: (.9, .9, .9)
       +        Rectangle:
       +            pos: self.x + sp(2), self.y + sp(2)
       +            size: self.width - sp(4), self.height - sp(4)
       +
       +
       +<SettingsItem@ButtonBehavior+BoxLayout>
       +    orientation: 'vertical'
       +    title: ''
       +    description: ''
       +    size_hint: 1, None
       +    height: '60dp'
       +    canvas.before:
       +        Color:
       +            rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 0)
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +    on_release:
       +        Clock.schedule_once(self.action)
       +    Widget
       +    TopLabel:
       +        id: title
       +        text: self.parent.title
       +        bold: True
       +        halign: 'left'
       +    TopLabel:
       +        text: self.parent.description
       +        color: 0.8, 0.8, 0.8, 1
       +        halign: 'left'
       +    Widget
       +
       +
       +
       +
       +<ScreenTabs@Screen>
       +    TabbedCarousel:
       +        id: panel
       +        tab_height: '48dp'
       +        tab_width: panel.width/3
       +        strip_border: 0, 0, 0, 0
       +        SendScreen:
       +            id: send_screen
       +            tab: send_tab
       +        HistoryScreen:
       +            id: history_screen
       +            tab: history_tab
       +        ReceiveScreen:
       +            id: receive_screen
       +            tab: receive_tab
       +        CleanHeader:
       +            id: send_tab
       +            text: _('Send')
       +            slide: 0
       +        CleanHeader:
       +            id: history_tab
       +            text: _('Balance')
       +            slide: 1
       +        CleanHeader:
       +            id: receive_tab
       +            text: _('Receive')
       +            slide: 2
       +
       +
       +<ActionOvrButton@ActionButton>
       +    #on_release:
       +        # fixme: the following line was commented out because it does not seem to do what it is intended
       +        # Clock.schedule_once(lambda dt: self.parent.parent.dismiss() if self.parent else None, 0.05)
       +    on_press:
       +        Clock.schedule_once(lambda dt: app.popup_dialog(self.name), 0.05)
       +        self.state = 'normal'
       +
       +
       +BoxLayout:
       +    orientation: 'vertical'
       +
       +    canvas.before:
       +        Color:
       +            rgb: .6, .6, .6
       +        Rectangle:
       +            size: self.size
       +            source: 'electrum/gui/kivy/data/background.png'
       +
       +    ActionBar:
       +
       +        ActionView:
       +            id: av
       +            ActionPrevious:
       +                app_icon: 'atlas://electrum/gui/kivy/theming/light/logo'
       +                app_icon_width: '100dp'
       +                with_previous: False
       +                size_hint_x: None
       +                on_release: app.popup_dialog('network')
       +
       +            ActionButton:
       +                id: action_status
       +                important: True
       +                size_hint: 1, 1
       +                bold: True
       +                color: 0.7, 0.7, 0.7, 1
       +                text: app.status
       +                font_size: '22dp'
       +                #minimum_width: '1dp'
       +                on_release: app.popup_dialog('status')
       +
       +            ActionOverflow:
       +                id: ao
       +                ActionOvrButton:
       +                    name: 'about'
       +                    text: _('About')
       +                ActionOvrButton:
       +                    name: 'wallets'
       +                    text: _('Wallets')
       +                ActionOvrButton:
       +                    name: 'network'
       +                    text: _('Network')
       +                ActionOvrButton:
       +                    name: 'settings'
       +                    text: _('Settings')
       +                    on_parent:
       +                        # when widget overflow drop down is shown, adjust the width
       +                        parent = args[1]
       +                        if parent: ao._dropdown.width = sp(200)
       +    ScreenManager:
       +        id: manager
       +        ScreenTabs:
       +            id: tabs
   DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
       t@@ -0,0 +1,1028 @@
       +import re
       +import os
       +import sys
       +import time
       +import datetime
       +import traceback
       +from decimal import Decimal
       +import threading
       +
       +from electrum.bitcoin import TYPE_ADDRESS
       +from electrum.storage import WalletStorage
       +from electrum.wallet import Wallet
       +from electrum.i18n import _
       +from electrum.paymentrequest import InvoiceStore
       +from electrum.util import profiler, InvalidPassword
       +from electrum.plugin import run_hook
       +from electrum.util import format_satoshis, format_satoshis_plain
       +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
       +
       +from kivy.app import App
       +from kivy.core.window import Window
       +from kivy.logger import Logger
       +from kivy.utils import platform
       +from kivy.properties import (OptionProperty, AliasProperty, ObjectProperty,
       +                             StringProperty, ListProperty, BooleanProperty, NumericProperty)
       +from kivy.cache import Cache
       +from kivy.clock import Clock
       +from kivy.factory import Factory
       +from kivy.metrics import inch
       +from kivy.lang import Builder
       +
       +## lazy imports for factory so that widgets can be used in kv
       +#Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard')
       +#Factory.register('InfoBubble', module='electrum.gui.kivy.uix.dialogs')
       +#Factory.register('OutputList', module='electrum.gui.kivy.uix.dialogs')
       +#Factory.register('OutputItem', module='electrum.gui.kivy.uix.dialogs')
       +
       +from .uix.dialogs.installwizard import InstallWizard
       +from .uix.dialogs import InfoBubble, crash_reporter
       +from .uix.dialogs import OutputList, OutputItem
       +from .uix.dialogs import TopLabel, RefLabel
       +
       +#from kivy.core.window import Window
       +#Window.softinput_mode = 'below_target'
       +
       +# delayed imports: for startup speed on android
       +notification = app = ref = None
       +util = False
       +
       +# register widget cache for keeping memory down timeout to forever to cache
       +# the data
       +Cache.register('electrum_widgets', timeout=0)
       +
       +from kivy.uix.screenmanager import Screen
       +from kivy.uix.tabbedpanel import TabbedPanel
       +from kivy.uix.label import Label
       +from kivy.core.clipboard import Clipboard
       +
       +Factory.register('TabbedCarousel', module='electrum.gui.kivy.uix.screens')
       +
       +# Register fonts without this you won't be able to use bold/italic...
       +# inside markup.
       +from kivy.core.text import Label
       +Label.register('Roboto',
       +               'electrum/gui/kivy/data/fonts/Roboto.ttf',
       +               'electrum/gui/kivy/data/fonts/Roboto.ttf',
       +               'electrum/gui/kivy/data/fonts/Roboto-Bold.ttf',
       +               'electrum/gui/kivy/data/fonts/Roboto-Bold.ttf')
       +
       +
       +from electrum.util import (base_units, NoDynamicFeeEstimates, decimal_point_to_base_unit_name,
       +                           base_unit_name_to_decimal_point, NotEnoughFunds)
       +
       +
       +class ElectrumWindow(App):
       +
       +    electrum_config = ObjectProperty(None)
       +    language = StringProperty('en')
       +
       +    # properties might be updated by the network
       +    num_blocks = NumericProperty(0)
       +    num_nodes = NumericProperty(0)
       +    server_host = StringProperty('')
       +    server_port = StringProperty('')
       +    num_chains = NumericProperty(0)
       +    blockchain_name = StringProperty('')
       +    fee_status = StringProperty('Fee')
       +    balance = StringProperty('')
       +    fiat_balance = StringProperty('')
       +    is_fiat = BooleanProperty(False)
       +    blockchain_checkpoint = NumericProperty(0)
       +
       +    auto_connect = BooleanProperty(False)
       +    def on_auto_connect(self, instance, x):
       +        host, port, protocol, proxy, auto_connect = self.network.get_parameters()
       +        self.network.set_parameters(host, port, protocol, proxy, self.auto_connect)
       +    def toggle_auto_connect(self, x):
       +        self.auto_connect = not self.auto_connect
       +
       +    def choose_server_dialog(self, popup):
       +        from .uix.dialogs.choice_dialog import ChoiceDialog
       +        protocol = 's'
       +        def cb2(host):
       +            from electrum import constants
       +            pp = servers.get(host, constants.net.DEFAULT_PORTS)
       +            port = pp.get(protocol, '')
       +            popup.ids.host.text = host
       +            popup.ids.port.text = port
       +        servers = self.network.get_servers()
       +        ChoiceDialog(_('Choose a server'), sorted(servers), popup.ids.host.text, cb2).open()
       +
       +    def choose_blockchain_dialog(self, dt):
       +        from .uix.dialogs.choice_dialog import ChoiceDialog
       +        chains = self.network.get_blockchains()
       +        def cb(name):
       +            for index, b in self.network.blockchains.items():
       +                if name == b.get_name():
       +                    self.network.follow_chain(index)
       +        names = [self.network.blockchains[b].get_name() for b in chains]
       +        if len(names) > 1:
       +            cur_chain = self.network.blockchain().get_name()
       +            ChoiceDialog(_('Choose your chain'), names, cur_chain, cb).open()
       +
       +    use_rbf = BooleanProperty(False)
       +    def on_use_rbf(self, instance, x):
       +        self.electrum_config.set_key('use_rbf', self.use_rbf, True)
       +
       +    use_change = BooleanProperty(False)
       +    def on_use_change(self, instance, x):
       +        self.electrum_config.set_key('use_change', self.use_change, True)
       +
       +    use_unconfirmed = BooleanProperty(False)
       +    def on_use_unconfirmed(self, instance, x):
       +        self.electrum_config.set_key('confirmed_only', not self.use_unconfirmed, True)
       +
       +    def set_URI(self, uri):
       +        self.switch_to('send')
       +        self.send_screen.set_URI(uri)
       +
       +    def on_new_intent(self, intent):
       +        if intent.getScheme() != 'bitcoin':
       +            return
       +        uri = intent.getDataString()
       +        self.set_URI(uri)
       +
       +    def on_language(self, instance, language):
       +        Logger.info('language: {}'.format(language))
       +        _.switch_lang(language)
       +
       +    def update_history(self, *dt):
       +        if self.history_screen:
       +            self.history_screen.update()
       +
       +    def on_quotes(self, d):
       +        Logger.info("on_quotes")
       +        self._trigger_update_history()
       +
       +    def on_history(self, d):
       +        Logger.info("on_history")
       +        self._trigger_update_history()
       +
       +    def _get_bu(self):
       +        decimal_point = self.electrum_config.get('decimal_point', 5)
       +        return decimal_point_to_base_unit_name(decimal_point)
       +
       +    def _set_bu(self, value):
       +        assert value in base_units.keys()
       +        decimal_point = base_unit_name_to_decimal_point(value)
       +        self.electrum_config.set_key('decimal_point', decimal_point, True)
       +        self._trigger_update_status()
       +        self._trigger_update_history()
       +
       +    base_unit = AliasProperty(_get_bu, _set_bu)
       +    status = StringProperty('')
       +    fiat_unit = StringProperty('')
       +
       +    def on_fiat_unit(self, a, b):
       +        self._trigger_update_history()
       +
       +    def decimal_point(self):
       +        return base_units[self.base_unit]
       +
       +    def btc_to_fiat(self, amount_str):
       +        if not amount_str:
       +            return ''
       +        if not self.fx.is_enabled():
       +            return ''
       +        rate = self.fx.exchange_rate()
       +        if rate.is_nan():
       +            return ''
       +        fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8)
       +        return "{:.2f}".format(fiat_amount).rstrip('0').rstrip('.')
       +
       +    def fiat_to_btc(self, fiat_amount):
       +        if not fiat_amount:
       +            return ''
       +        rate = self.fx.exchange_rate()
       +        if rate.is_nan():
       +            return ''
       +        satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate))
       +        return format_satoshis_plain(satoshis, self.decimal_point())
       +
       +    def get_amount(self, amount_str):
       +        a, u = amount_str.split()
       +        assert u == self.base_unit
       +        try:
       +            x = Decimal(a)
       +        except:
       +            return None
       +        p = pow(10, self.decimal_point())
       +        return int(p * x)
       +
       +
       +    _orientation = OptionProperty('landscape',
       +                                 options=('landscape', 'portrait'))
       +
       +    def _get_orientation(self):
       +        return self._orientation
       +
       +    orientation = AliasProperty(_get_orientation,
       +                                None,
       +                                bind=('_orientation',))
       +    '''Tries to ascertain the kind of device the app is running on.
       +    Cane be one of `tablet` or `phone`.
       +
       +    :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape'
       +    '''
       +
       +    _ui_mode = OptionProperty('phone', options=('tablet', 'phone'))
       +
       +    def _get_ui_mode(self):
       +        return self._ui_mode
       +
       +    ui_mode = AliasProperty(_get_ui_mode,
       +                            None,
       +                            bind=('_ui_mode',))
       +    '''Defines tries to ascertain the kind of device the app is running on.
       +    Cane be one of `tablet` or `phone`.
       +
       +    :data:`ui_mode` is a read only `AliasProperty` Defaults to 'phone'
       +    '''
       +
       +    def __init__(self, **kwargs):
       +        # initialize variables
       +        self._clipboard = Clipboard
       +        self.info_bubble = None
       +        self.nfcscanner = None
       +        self.tabs = None
       +        self.is_exit = False
       +        self.wallet = None
       +        self.pause_time = 0
       +
       +        App.__init__(self)#, **kwargs)
       +
       +        title = _('Electrum App')
       +        self.electrum_config = config = kwargs.get('config', None)
       +        self.language = config.get('language', 'en')
       +        self.network = network = kwargs.get('network', None)
       +        if self.network:
       +            self.num_blocks = self.network.get_local_height()
       +            self.num_nodes = len(self.network.get_interfaces())
       +            host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()
       +            self.server_host = host
       +            self.server_port = port
       +            self.auto_connect = auto_connect
       +            self.proxy_config = proxy_config if proxy_config else {}
       +
       +        self.plugins = kwargs.get('plugins', [])
       +        self.gui_object = kwargs.get('gui_object', None)
       +        self.daemon = self.gui_object.daemon
       +        self.fx = self.daemon.fx
       +
       +        self.use_rbf = config.get('use_rbf', True)
       +        self.use_change = config.get('use_change', True)
       +        self.use_unconfirmed = not config.get('confirmed_only', False)
       +
       +        # create triggers so as to minimize updating a max of 2 times a sec
       +        self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5)
       +        self._trigger_update_status = Clock.create_trigger(self.update_status, .5)
       +        self._trigger_update_history = Clock.create_trigger(self.update_history, .5)
       +        self._trigger_update_interfaces = Clock.create_trigger(self.update_interfaces, .5)
       +        # cached dialogs
       +        self._settings_dialog = None
       +        self._password_dialog = None
       +        self.fee_status = self.electrum_config.get_fee_status()
       +
       +    def wallet_name(self):
       +        return os.path.basename(self.wallet.storage.path) if self.wallet else ' '
       +
       +    def on_pr(self, pr):
       +        if not self.wallet:
       +            self.show_error(_('No wallet loaded.'))
       +            return
       +        if pr.verify(self.wallet.contacts):
       +            key = self.wallet.invoices.add(pr)
       +            if self.invoices_screen:
       +                self.invoices_screen.update()
       +            status = self.wallet.invoices.get_status(key)
       +            if status == PR_PAID:
       +                self.show_error("invoice already paid")
       +                self.send_screen.do_clear()
       +            else:
       +                if pr.has_expired():
       +                    self.show_error(_('Payment request has expired'))
       +                else:
       +                    self.switch_to('send')
       +                    self.send_screen.set_request(pr)
       +        else:
       +            self.show_error("invoice error:" + pr.error)
       +            self.send_screen.do_clear()
       +
       +    def on_qr(self, data):
       +        from electrum.bitcoin import base_decode, is_address
       +        data = data.strip()
       +        if is_address(data):
       +            self.set_URI(data)
       +            return
       +        if data.startswith('bitcoin:'):
       +            self.set_URI(data)
       +            return
       +        # try to decode transaction
       +        from electrum.transaction import Transaction
       +        from electrum.util import bh2u
       +        try:
       +            text = bh2u(base_decode(data, None, base=43))
       +            tx = Transaction(text)
       +            tx.deserialize()
       +        except:
       +            tx = None
       +        if tx:
       +            self.tx_dialog(tx)
       +            return
       +        # show error
       +        self.show_error("Unable to decode QR data")
       +
       +    def update_tab(self, name):
       +        s = getattr(self, name + '_screen', None)
       +        if s:
       +            s.update()
       +
       +    @profiler
       +    def update_tabs(self):
       +        for tab in ['invoices', 'send', 'history', 'receive', 'address']:
       +            self.update_tab(tab)
       +
       +    def switch_to(self, name):
       +        s = getattr(self, name + '_screen', None)
       +        if s is None:
       +            s = self.tabs.ids[name + '_screen']
       +            s.load_screen()
       +        panel = self.tabs.ids.panel
       +        tab = self.tabs.ids[name + '_tab']
       +        panel.switch_to(tab)
       +
       +    def show_request(self, addr):
       +        self.switch_to('receive')
       +        self.receive_screen.screen.address = addr
       +
       +    def show_pr_details(self, req, status, is_invoice):
       +        from electrum.util import format_time
       +        requestor = req.get('requestor')
       +        exp = req.get('exp')
       +        memo = req.get('memo')
       +        amount = req.get('amount')
       +        fund = req.get('fund')
       +        popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/invoice.kv')
       +        popup.is_invoice = is_invoice
       +        popup.amount = amount
       +        popup.requestor = requestor if is_invoice else req.get('address')
       +        popup.exp = format_time(exp) if exp else ''
       +        popup.description = memo if memo else ''
       +        popup.signature = req.get('signature', '')
       +        popup.status = status
       +        popup.fund = fund if fund else 0
       +        txid = req.get('txid')
       +        popup.tx_hash = txid or ''
       +        popup.on_open = lambda: popup.ids.output_list.update(req.get('outputs', []))
       +        popup.export = self.export_private_keys
       +        popup.open()
       +
       +    def show_addr_details(self, req, status):
       +        from electrum.util import format_time
       +        fund = req.get('fund')
       +        isaddr = 'y'
       +        popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/invoice.kv')
       +        popup.isaddr = isaddr
       +        popup.is_invoice = False
       +        popup.status = status
       +        popup.requestor = req.get('address')
       +        popup.fund = fund if fund else 0
       +        popup.export = self.export_private_keys
       +        popup.open()
       +
       +    def qr_dialog(self, title, data, show_text=False):
       +        from .uix.dialogs.qr_dialog import QRDialog
       +        popup = QRDialog(title, data, show_text)
       +        popup.open()
       +
       +    def scan_qr(self, on_complete):
       +        if platform != 'android':
       +            return
       +        from jnius import autoclass, cast
       +        from android import activity
       +        PythonActivity = autoclass('org.kivy.android.PythonActivity')
       +        SimpleScannerActivity = autoclass("org.electrum.qr.SimpleScannerActivity")
       +        Intent = autoclass('android.content.Intent')
       +        intent = Intent(PythonActivity.mActivity, SimpleScannerActivity)
       +
       +        def on_qr_result(requestCode, resultCode, intent):
       +            try:
       +                if resultCode == -1:  # RESULT_OK:
       +                    #  this doesn't work due to some bug in jnius:
       +                    # contents = intent.getStringExtra("text")
       +                    String = autoclass("java.lang.String")
       +                    contents = intent.getStringExtra(String("text"))
       +                    on_complete(contents)
       +            finally:
       +                activity.unbind(on_activity_result=on_qr_result)
       +        activity.bind(on_activity_result=on_qr_result)
       +        PythonActivity.mActivity.startActivityForResult(intent, 0)
       +
       +    def do_share(self, data, title):
       +        if platform != 'android':
       +            return
       +        from jnius import autoclass, cast
       +        JS = autoclass('java.lang.String')
       +        Intent = autoclass('android.content.Intent')
       +        sendIntent = Intent()
       +        sendIntent.setAction(Intent.ACTION_SEND)
       +        sendIntent.setType("text/plain")
       +        sendIntent.putExtra(Intent.EXTRA_TEXT, JS(data))
       +        PythonActivity = autoclass('org.kivy.android.PythonActivity')
       +        currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
       +        it = Intent.createChooser(sendIntent, cast('java.lang.CharSequence', JS(title)))
       +        currentActivity.startActivity(it)
       +
       +    def build(self):
       +        return Builder.load_file('electrum/gui/kivy/main.kv')
       +
       +    def _pause(self):
       +        if platform == 'android':
       +            # move activity to back
       +            from jnius import autoclass
       +            python_act = autoclass('org.kivy.android.PythonActivity')
       +            mActivity = python_act.mActivity
       +            mActivity.moveTaskToBack(True)
       +
       +    def on_start(self):
       +        ''' This is the start point of the kivy ui
       +        '''
       +        import time
       +        Logger.info('Time to on_start: {} <<<<<<<<'.format(time.clock()))
       +        win = Window
       +        win.bind(size=self.on_size, on_keyboard=self.on_keyboard)
       +        win.bind(on_key_down=self.on_key_down)
       +        #win.softinput_mode = 'below_target'
       +        self.on_size(win, win.size)
       +        self.init_ui()
       +        crash_reporter.ExceptionHook(self)
       +        # init plugins
       +        run_hook('init_kivy', self)
       +        # fiat currency
       +        self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''
       +        # default tab
       +        self.switch_to('history')
       +        # bind intent for bitcoin: URI scheme
       +        if platform == 'android':
       +            from android import activity
       +            from jnius import autoclass
       +            PythonActivity = autoclass('org.kivy.android.PythonActivity')
       +            mactivity = PythonActivity.mActivity
       +            self.on_new_intent(mactivity.getIntent())
       +            activity.bind(on_new_intent=self.on_new_intent)
       +        # connect callbacks
       +        if self.network:
       +            interests = ['updated', 'status', 'new_transaction', 'verified', 'interfaces']
       +            self.network.register_callback(self.on_network_event, interests)
       +            self.network.register_callback(self.on_fee, ['fee'])
       +            self.network.register_callback(self.on_quotes, ['on_quotes'])
       +            self.network.register_callback(self.on_history, ['on_history'])
       +        # load wallet
       +        self.load_wallet_by_name(self.electrum_config.get_wallet_path())
       +        # URI passed in config
       +        uri = self.electrum_config.get('url')
       +        if uri:
       +            self.set_URI(uri)
       +
       +
       +    def get_wallet_path(self):
       +        if self.wallet:
       +            return self.wallet.storage.path
       +        else:
       +            return ''
       +
       +    def on_wizard_complete(self, wizard, wallet):
       +        if wallet:  # wizard returned a wallet
       +            wallet.start_threads(self.daemon.network)
       +            self.daemon.add_wallet(wallet)
       +            self.load_wallet(wallet)
       +        elif not self.wallet:
       +            # wizard did not return a wallet; and there is no wallet open atm
       +            # try to open last saved wallet (potentially start wizard again)
       +            self.load_wallet_by_name(self.electrum_config.get_wallet_path(), ask_if_wizard=True)
       +
       +    def load_wallet_by_name(self, path, ask_if_wizard=False):
       +        if not path:
       +            return
       +        if self.wallet and self.wallet.storage.path == path:
       +            return
       +        wallet = self.daemon.load_wallet(path, None)
       +        if wallet:
       +            if wallet.has_password():
       +                self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop)
       +            else:
       +                self.load_wallet(wallet)
       +        else:
       +            Logger.debug('Electrum: Wallet not found or action needed. Launching install wizard')
       +
       +            def launch_wizard():
       +                storage = WalletStorage(path, manual_upgrades=True)
       +                wizard = Factory.InstallWizard(self.electrum_config, self.plugins, storage)
       +                wizard.bind(on_wizard_complete=self.on_wizard_complete)
       +                action = wizard.storage.get_action()
       +                wizard.run(action)
       +            if not ask_if_wizard:
       +                launch_wizard()
       +            else:
       +                from .uix.dialogs.question import Question
       +
       +                def handle_answer(b: bool):
       +                    if b:
       +                        launch_wizard()
       +                    else:
       +                        try: os.unlink(path)
       +                        except FileNotFoundError: pass
       +                        self.stop()
       +                d = Question(_('Do you want to launch the wizard again?'), handle_answer)
       +                d.open()
       +
       +    def on_stop(self):
       +        Logger.info('on_stop')
       +        if self.wallet:
       +            self.electrum_config.save_last_wallet(self.wallet)
       +        self.stop_wallet()
       +
       +    def stop_wallet(self):
       +        if self.wallet:
       +            self.daemon.stop_wallet(self.wallet.storage.path)
       +            self.wallet = None
       +
       +    def on_key_down(self, instance, key, keycode, codepoint, modifiers):
       +        if 'ctrl' in modifiers:
       +            # q=24 w=25
       +            if keycode in (24, 25):
       +                self.stop()
       +            elif keycode == 27:
       +                # r=27
       +                # force update wallet
       +                self.update_wallet()
       +            elif keycode == 112:
       +                # pageup
       +                #TODO move to next tab
       +                pass
       +            elif keycode == 117:
       +                # pagedown
       +                #TODO move to prev tab
       +                pass
       +        #TODO: alt+tab_number to activate the particular tab
       +
       +    def on_keyboard(self, instance, key, keycode, codepoint, modifiers):
       +        if key == 27 and self.is_exit is False:
       +            self.is_exit = True
       +            self.show_info(_('Press again to exit'))
       +            return True
       +        # override settings button
       +        if key in (319, 282): #f1/settings button on android
       +            #self.gui.main_gui.toggle_settings(self)
       +            return True
       +
       +    def settings_dialog(self):
       +        from .uix.dialogs.settings import SettingsDialog
       +        if self._settings_dialog is None:
       +            self._settings_dialog = SettingsDialog(self)
       +        self._settings_dialog.update()
       +        self._settings_dialog.open()
       +
       +    def popup_dialog(self, name):
       +        if name == 'settings':
       +            self.settings_dialog()
       +        elif name == 'wallets':
       +            from .uix.dialogs.wallets import WalletDialog
       +            d = WalletDialog()
       +            d.open()
       +        elif name == 'status':
       +            popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/'+name+'.kv')
       +            master_public_keys_layout = popup.ids.master_public_keys
       +            for xpub in self.wallet.get_master_public_keys()[1:]:
       +                master_public_keys_layout.add_widget(TopLabel(text=_('Master Public Key')))
       +                ref = RefLabel()
       +                ref.name = _('Master Public Key')
       +                ref.data = xpub
       +                master_public_keys_layout.add_widget(ref)
       +            popup.open()
       +        else:
       +            popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/'+name+'.kv')
       +            popup.open()
       +
       +    @profiler
       +    def init_ui(self):
       +        ''' Initialize The Ux part of electrum. This function performs the basic
       +        tasks of setting up the ui.
       +        '''
       +        #from weakref import ref
       +
       +        self.funds_error = False
       +        # setup UX
       +        self.screens = {}
       +
       +        #setup lazy imports for mainscreen
       +        Factory.register('AnimatedPopup',
       +                         module='electrum.gui.kivy.uix.dialogs')
       +        Factory.register('QRCodeWidget',
       +                         module='electrum.gui.kivy.uix.qrcodewidget')
       +
       +        # preload widgets. Remove this if you want to load the widgets on demand
       +        #Cache.append('electrum_widgets', 'AnimatedPopup', Factory.AnimatedPopup())
       +        #Cache.append('electrum_widgets', 'QRCodeWidget', Factory.QRCodeWidget())
       +
       +        # load and focus the ui
       +        self.root.manager = self.root.ids['manager']
       +
       +        self.history_screen = None
       +        self.contacts_screen = None
       +        self.send_screen = None
       +        self.invoices_screen = None
       +        self.receive_screen = None
       +        self.requests_screen = None
       +        self.address_screen = None
       +        self.icon = "icons/electrum.png"
       +        self.tabs = self.root.ids['tabs']
       +
       +    def update_interfaces(self, dt):
       +        self.num_nodes = len(self.network.get_interfaces())
       +        self.num_chains = len(self.network.get_blockchains())
       +        chain = self.network.blockchain()
       +        self.blockchain_checkpoint = chain.get_checkpoint()
       +        self.blockchain_name = chain.get_name()
       +        interface = self.network.interface
       +        if interface:
       +            self.server_host = interface.host
       +
       +    def on_network_event(self, event, *args):
       +        Logger.info('network event: '+ event)
       +        if event == 'interfaces':
       +            self._trigger_update_interfaces()
       +        elif event == 'updated':
       +            self._trigger_update_wallet()
       +            self._trigger_update_status()
       +        elif event == 'status':
       +            self._trigger_update_status()
       +        elif event == 'new_transaction':
       +            self._trigger_update_wallet()
       +        elif event == 'verified':
       +            self._trigger_update_wallet()
       +
       +    @profiler
       +    def load_wallet(self, wallet):
       +        if self.wallet:
       +            self.stop_wallet()
       +        self.wallet = wallet
       +        self.update_wallet()
       +        # Once GUI has been initialized check if we want to announce something
       +        # since the callback has been called before the GUI was initialized
       +        if self.receive_screen:
       +            self.receive_screen.clear()
       +        self.update_tabs()
       +        run_hook('load_wallet', wallet, self)
       +
       +    def update_status(self, *dt):
       +        self.num_blocks = self.network.get_local_height()
       +        if not self.wallet:
       +            self.status = _("No Wallet")
       +            return
       +        if self.network is None or not self.network.is_running():
       +            status = _("Offline")
       +        elif self.network.is_connected():
       +            server_height = self.network.get_server_height()
       +            server_lag = self.network.get_local_height() - server_height
       +            if not self.wallet.up_to_date or server_height == 0:
       +                status = _("Synchronizing...")
       +            elif server_lag > 1:
       +                status = _("Server lagging")
       +            else:
       +                status = ''
       +        else:
       +            status = _("Disconnected")
       +        self.status = self.wallet.basename() + (' [size=15dp](%s)[/size]'%status if status else '')
       +        # balance
       +        c, u, x = self.wallet.get_balance()
       +        text = self.format_amount(c+x+u)
       +        self.balance = str(text.strip()) + ' [size=22dp]%s[/size]'% self.base_unit
       +        self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy
       +
       +    def get_max_amount(self):
       +        if run_hook('abort_send', self):
       +            return ''
       +        inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
       +        if not inputs:
       +            return ''
       +        addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
       +        outputs = [(TYPE_ADDRESS, addr, '!')]
       +        try:
       +            tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
       +        except NoDynamicFeeEstimates as e:
       +            Clock.schedule_once(lambda dt, bound_e=e: self.show_error(str(bound_e)))
       +            return ''
       +        except NotEnoughFunds:
       +            return ''
       +        amount = tx.output_value()
       +        __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
       +        amount_after_all_fees = amount - x_fee_amount
       +        return format_satoshis_plain(amount_after_all_fees, self.decimal_point())
       +
       +    def format_amount(self, x, is_diff=False, whitespaces=False):
       +        return format_satoshis(x, 0, self.decimal_point(), is_diff=is_diff, whitespaces=whitespaces)
       +
       +    def format_amount_and_units(self, x):
       +        return format_satoshis_plain(x, self.decimal_point()) + ' ' + self.base_unit
       +
       +    #@profiler
       +    def update_wallet(self, *dt):
       +        self._trigger_update_status()
       +        if self.wallet and (self.wallet.up_to_date or not self.network or not self.network.is_connected()):
       +            self.update_tabs()
       +
       +    def notify(self, message):
       +        try:
       +            global notification, os
       +            if not notification:
       +                from plyer import notification
       +            icon = (os.path.dirname(os.path.realpath(__file__))
       +                    + '/../../' + self.icon)
       +            notification.notify('Electrum', message,
       +                            app_icon=icon, app_name='Electrum')
       +        except ImportError:
       +            Logger.Error('Notification: needs plyer; `sudo pip install plyer`')
       +
       +    def on_pause(self):
       +        self.pause_time = time.time()
       +        # pause nfc
       +        if self.nfcscanner:
       +            self.nfcscanner.nfc_disable()
       +        return True
       +
       +    def on_resume(self):
       +        now = time.time()
       +        if self.wallet and self.wallet.has_password() and now - self.pause_time > 60:
       +            self.password_dialog(self.wallet, _('Enter PIN'), None, self.stop)
       +        if self.nfcscanner:
       +            self.nfcscanner.nfc_enable()
       +
       +    def on_size(self, instance, value):
       +        width, height = value
       +        self._orientation = 'landscape' if width > height else 'portrait'
       +        self._ui_mode = 'tablet' if min(width, height) > inch(3.51) else 'phone'
       +
       +    def on_ref_label(self, label, touch):
       +        if label.touched:
       +            label.touched = False
       +            self.qr_dialog(label.name, label.data, True)
       +        else:
       +            label.touched = True
       +            self._clipboard.copy(label.data)
       +            Clock.schedule_once(lambda dt: self.show_info(_('Text copied to clipboard.\nTap again to display it as QR code.')))
       +
       +    def set_send(self, address, amount, label, message):
       +        self.send_payment(address, amount=amount, label=label, message=message)
       +
       +    def show_error(self, error, width='200dp', pos=None, arrow_pos=None,
       +        exit=False, icon='atlas://electrum/gui/kivy/theming/light/error', duration=0,
       +        modal=False):
       +        ''' Show an error Message Bubble.
       +        '''
       +        self.show_info_bubble( text=error, icon=icon, width=width,
       +            pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit,
       +            duration=duration, modal=modal)
       +
       +    def show_info(self, error, width='200dp', pos=None, arrow_pos=None,
       +        exit=False, duration=0, modal=False):
       +        ''' Show an Info Message Bubble.
       +        '''
       +        self.show_error(error, icon='atlas://electrum/gui/kivy/theming/light/important',
       +            duration=duration, modal=modal, exit=exit, pos=pos,
       +            arrow_pos=arrow_pos)
       +
       +    def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0,
       +        arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False):
       +        '''Method to show an Information Bubble
       +
       +        .. parameters::
       +            text: Message to be displayed
       +            pos: position for the bubble
       +            duration: duration the bubble remains on screen. 0 = click to hide
       +            width: width of the Bubble
       +            arrow_pos: arrow position for the bubble
       +        '''
       +        info_bubble = self.info_bubble
       +        if not info_bubble:
       +            info_bubble = self.info_bubble = Factory.InfoBubble()
       +
       +        win = Window
       +        if info_bubble.parent:
       +            win.remove_widget(info_bubble
       +                                 if not info_bubble.modal else
       +                                 info_bubble._modal_view)
       +
       +        if not arrow_pos:
       +            info_bubble.show_arrow = False
       +        else:
       +            info_bubble.show_arrow = True
       +            info_bubble.arrow_pos = arrow_pos
       +        img = info_bubble.ids.img
       +        if text == 'texture':
       +            # icon holds a texture not a source image
       +            # display the texture in full screen
       +            text = ''
       +            img.texture = icon
       +            info_bubble.fs = True
       +            info_bubble.show_arrow = False
       +            img.allow_stretch = True
       +            info_bubble.dim_background = True
       +            info_bubble.background_image = 'atlas://electrum/gui/kivy/theming/light/card'
       +        else:
       +            info_bubble.fs = False
       +            info_bubble.icon = icon
       +            #if img.texture and img._coreimage:
       +            #    img.reload()
       +            img.allow_stretch = False
       +            info_bubble.dim_background = False
       +            info_bubble.background_image = 'atlas://data/images/defaulttheme/bubble'
       +        info_bubble.message = text
       +        if not pos:
       +            pos = (win.center[0], win.center[1] - (info_bubble.height/2))
       +        info_bubble.show(pos, duration, width, modal=modal, exit=exit)
       +
       +    def tx_dialog(self, tx):
       +        from .uix.dialogs.tx_dialog import TxDialog
       +        d = TxDialog(self, tx)
       +        d.open()
       +
       +    def sign_tx(self, *args):
       +        threading.Thread(target=self._sign_tx, args=args).start()
       +
       +    def _sign_tx(self, tx, password, on_success, on_failure):
       +        try:
       +            self.wallet.sign_transaction(tx, password)
       +        except InvalidPassword:
       +            Clock.schedule_once(lambda dt: on_failure(_("Invalid PIN")))
       +            return
       +        on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
       +        Clock.schedule_once(lambda dt: on_success(tx))
       +
       +    def _broadcast_thread(self, tx, on_complete):
       +        ok, txid = self.network.broadcast_transaction(tx)
       +        Clock.schedule_once(lambda dt: on_complete(ok, txid))
       +
       +    def broadcast(self, tx, pr=None):
       +        def on_complete(ok, msg):
       +            if ok:
       +                self.show_info(_('Payment sent.'))
       +                if self.send_screen:
       +                    self.send_screen.do_clear()
       +                if pr:
       +                    self.wallet.invoices.set_paid(pr, tx.txid())
       +                    self.wallet.invoices.save()
       +                    self.update_tab('invoices')
       +            else:
       +                self.show_error(msg)
       +
       +        if self.network and self.network.is_connected():
       +            self.show_info(_('Sending'))
       +            threading.Thread(target=self._broadcast_thread, args=(tx, on_complete)).start()
       +        else:
       +            self.show_info(_('Cannot broadcast transaction') + ':\n' + _('Not connected'))
       +
       +    def description_dialog(self, screen):
       +        from .uix.dialogs.label_dialog import LabelDialog
       +        text = screen.message
       +        def callback(text):
       +            screen.message = text
       +        d = LabelDialog(_('Enter description'), text, callback)
       +        d.open()
       +
       +    def amount_dialog(self, screen, show_max):
       +        from .uix.dialogs.amount_dialog import AmountDialog
       +        amount = screen.amount
       +        if amount:
       +            amount, u = str(amount).split()
       +            assert u == self.base_unit
       +        def cb(amount):
       +            screen.amount = amount
       +        popup = AmountDialog(show_max, amount, cb)
       +        popup.open()
       +
       +    def invoices_dialog(self, screen):
       +        from .uix.dialogs.invoices import InvoicesDialog
       +        if len(self.wallet.invoices.sorted_list()) == 0:
       +            self.show_info(' '.join([
       +                _('No saved invoices.'),
       +                _('Signed invoices are saved automatically when you scan them.'),
       +                _('You may also save unsigned requests or contact addresses using the save button.')
       +            ]))
       +            return
       +        popup = InvoicesDialog(self, screen, None)
       +        popup.update()
       +        popup.open()
       +
       +    def requests_dialog(self, screen):
       +        from .uix.dialogs.requests import RequestsDialog
       +        if len(self.wallet.get_sorted_requests(self.electrum_config)) == 0:
       +            self.show_info(_('No saved requests.'))
       +            return
       +        popup = RequestsDialog(self, screen, None)
       +        popup.update()
       +        popup.open()
       +
       +    def addresses_dialog(self, screen):
       +        from .uix.dialogs.addresses import AddressesDialog
       +        popup = AddressesDialog(self, screen, None)
       +        popup.update()
       +        popup.open()
       +
       +    def fee_dialog(self, label, dt):
       +        from .uix.dialogs.fee_dialog import FeeDialog
       +        def cb():
       +            self.fee_status = self.electrum_config.get_fee_status()
       +        fee_dialog = FeeDialog(self, self.electrum_config, cb)
       +        fee_dialog.open()
       +
       +    def on_fee(self, event, *arg):
       +        self.fee_status = self.electrum_config.get_fee_status()
       +
       +    def protected(self, msg, f, args):
       +        if self.wallet.has_password():
       +            on_success = lambda pw: f(*(args + (pw,)))
       +            self.password_dialog(self.wallet, msg, on_success, lambda: None)
       +        else:
       +            f(*(args + (None,)))
       +
       +    def delete_wallet(self):
       +        from .uix.dialogs.question import Question
       +        basename = os.path.basename(self.wallet.storage.path)
       +        d = Question(_('Delete wallet?') + '\n' + basename, self._delete_wallet)
       +        d.open()
       +
       +    def _delete_wallet(self, b):
       +        if b:
       +            basename = self.wallet.basename()
       +            self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ())
       +
       +    def __delete_wallet(self, pw):
       +        wallet_path = self.get_wallet_path()
       +        dirname = os.path.dirname(wallet_path)
       +        basename = os.path.basename(wallet_path)
       +        if self.wallet.has_password():
       +            try:
       +                self.wallet.check_password(pw)
       +            except:
       +                self.show_error("Invalid PIN")
       +                return
       +        self.stop_wallet()
       +        os.unlink(wallet_path)
       +        self.show_error(_("Wallet removed: {}").format(basename))
       +        new_path = self.electrum_config.get_wallet_path()
       +        self.load_wallet_by_name(new_path)
       +
       +    def show_seed(self, label):
       +        self.protected(_("Enter your PIN code in order to decrypt your seed"), self._show_seed, (label,))
       +
       +    def _show_seed(self, label, password):
       +        if self.wallet.has_password() and password is None:
       +            return
       +        keystore = self.wallet.keystore
       +        try:
       +            seed = keystore.get_seed(password)
       +            passphrase = keystore.get_passphrase(password)
       +        except:
       +            self.show_error("Invalid PIN")
       +            return
       +        label.text = _('Seed') + ':\n' + seed
       +        if passphrase:
       +            label.text += '\n\n' + _('Passphrase') + ': ' + passphrase
       +
       +    def password_dialog(self, wallet, msg, on_success, on_failure):
       +        from .uix.dialogs.password_dialog import PasswordDialog
       +        if self._password_dialog is None:
       +            self._password_dialog = PasswordDialog()
       +        self._password_dialog.init(self, wallet, msg, on_success, on_failure)
       +        self._password_dialog.open()
       +
       +    def change_password(self, cb):
       +        from .uix.dialogs.password_dialog import PasswordDialog
       +        if self._password_dialog is None:
       +            self._password_dialog = PasswordDialog()
       +        message = _("Changing PIN code.") + '\n' + _("Enter your current PIN:")
       +        def on_success(old_password, new_password):
       +            self.wallet.update_password(old_password, new_password)
       +            self.show_info(_("Your PIN code was updated"))
       +        on_failure = lambda: self.show_error(_("PIN codes do not match"))
       +        self._password_dialog.init(self, self.wallet, message, on_success, on_failure, is_change=1)
       +        self._password_dialog.open()
       +
       +    def export_private_keys(self, pk_label, addr):
       +        if self.wallet.is_watching_only():
       +            self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))
       +            return
       +        def show_private_key(addr, pk_label, password):
       +            if self.wallet.has_password() and password is None:
       +                return
       +            if not self.wallet.can_export():
       +                return
       +            try:
       +                key = str(self.wallet.export_private_key(addr, password)[0])
       +                pk_label.data = key
       +            except InvalidPassword:
       +                self.show_error("Invalid PIN")
       +                return
       +        self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label))
   DIR diff --git a/electrum/gui/kivy/nfc_scanner/__init__.py b/electrum/gui/kivy/nfc_scanner/__init__.py
       t@@ -0,0 +1,44 @@
       +__all__ = ('NFCBase', 'NFCScanner')
       +
       +class NFCBase(Widget):
       +    ''' This is the base Abstract definition class that the actual hardware dependent
       +    implementations would be based on. If you want to define a feature that is
       +    accessible and implemented by every platform implementation then define that
       +    method in this class.
       +    '''
       +
       +    payload = ObjectProperty(None)
       +    '''This is the data gotten from the tag. 
       +    '''
       +
       +    def nfc_init(self):
       +        ''' Initialize the adapter.
       +        '''
       +        pass
       +
       +    def nfc_disable(self):
       +        ''' Disable scanning
       +        '''
       +        pass
       +
       +    def nfc_enable(self):
       +        ''' Enable Scanning
       +        '''
       +        pass
       +
       +    def nfc_enable_exchange(self, data):
       +        ''' Enable P2P Ndef exchange
       +        '''
       +        pass
       +
       +    def nfc_disable_exchange(self):
       +        ''' Disable/Stop P2P Ndef exchange
       +        '''
       +        pass
       +
       +# load NFCScanner implementation
       +
       +NFCScanner = core_select_lib('nfc_manager', (
       +    # keep the dummy implementation as the last one to make it the fallback provider.NFCScanner = core_select_lib('nfc_scanner', (
       +    ('android', 'scanner_android', 'ScannerAndroid'),
       +    ('dummy', 'scanner_dummy', 'ScannerDummy')), True, 'electrum.gui.kivy')
   DIR diff --git a/electrum/gui/kivy/nfc_scanner/scanner_android.py b/electrum/gui/kivy/nfc_scanner/scanner_android.py
       t@@ -0,0 +1,242 @@
       +'''This is the Android implementation of NFC Scanning using the
       +built in NFC adapter of some android phones.
       +'''
       +
       +from kivy.app import App
       +from kivy.clock import Clock
       +#Detect which platform we are on
       +from kivy.utils import platform
       +if platform != 'android':
       +    raise ImportError
       +import threading
       +
       +from . import NFCBase
       +from jnius import autoclass, cast
       +from android.runnable import run_on_ui_thread
       +from android import activity
       +
       +BUILDVERSION = autoclass('android.os.Build$VERSION').SDK_INT
       +NfcAdapter = autoclass('android.nfc.NfcAdapter')
       +PythonActivity = autoclass('org.kivy.android.PythonActivity')
       +JString = autoclass('java.lang.String')
       +Charset = autoclass('java.nio.charset.Charset')
       +locale = autoclass('java.util.Locale')
       +Intent = autoclass('android.content.Intent')
       +IntentFilter = autoclass('android.content.IntentFilter')
       +PendingIntent = autoclass('android.app.PendingIntent')
       +Ndef = autoclass('android.nfc.tech.Ndef')
       +NdefRecord = autoclass('android.nfc.NdefRecord')
       +NdefMessage = autoclass('android.nfc.NdefMessage')
       +
       +app = None
       +
       +
       +
       +class ScannerAndroid(NFCBase):
       +    ''' This is the class responsible for handling the interface with the
       +    Android NFC adapter. See Module Documentation for details.
       +    '''
       +
       +    name = 'NFCAndroid'
       +
       +    def nfc_init(self):
       +        ''' This is where we initialize NFC adapter.
       +        '''
       +        # Initialize NFC
       +        global app
       +        app = App.get_running_app()
       +
       +        # Make sure we are listening to new intent 
       +        activity.bind(on_new_intent=self.on_new_intent)
       +
       +        # Configure nfc
       +        self.j_context = context = PythonActivity.mActivity
       +        self.nfc_adapter = NfcAdapter.getDefaultAdapter(context)
       +        # Check if adapter exists
       +        if not self.nfc_adapter:
       +            return False
       +        
       +        # specify that we want our activity to remain on top when a new intent
       +        # is fired
       +        self.nfc_pending_intent = PendingIntent.getActivity(context, 0,
       +            Intent(context, context.getClass()).addFlags(
       +                Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
       +
       +        # Filter for different types of action, by default we enable all.
       +        # These are only for handling different NFC technologies when app is in foreground
       +        self.ndef_detected = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
       +        #self.tech_detected = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
       +        #self.tag_detected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
       +
       +        # setup tag discovery for ourt tag type
       +        try:
       +            self.ndef_detected.addCategory(Intent.CATEGORY_DEFAULT)
       +            # setup the foreground dispatch to detect all mime types
       +            self.ndef_detected.addDataType('*/*')
       +
       +            self.ndef_exchange_filters = [self.ndef_detected]
       +        except Exception as err:
       +            raise Exception(repr(err))
       +        return True
       +
       +    def get_ndef_details(self, tag):
       +        ''' Get all the details from the tag.
       +        '''
       +        details = {}
       +
       +        try:
       +            #print 'id'
       +            details['uid'] = ':'.join(['{:02x}'.format(bt & 0xff) for bt in tag.getId()])
       +            #print 'technologies'
       +            details['Technologies'] = tech_list = [tech.split('.')[-1] for tech in tag.getTechList()]
       +            #print 'get NDEF tag details'
       +            ndefTag = cast('android.nfc.tech.Ndef', Ndef.get(tag))
       +            #print 'tag size'
       +            details['MaxSize'] = ndefTag.getMaxSize()
       +            #details['usedSize'] = '0'
       +            #print 'is tag writable?'
       +            details['writable'] = ndefTag.isWritable()
       +            #print 'Data format'
       +            # Can be made readonly
       +            # get NDEF message details
       +            ndefMesg = ndefTag.getCachedNdefMessage()
       +            # get size of current records
       +            details['consumed'] = len(ndefMesg.toByteArray())
       +            #print 'tag type'
       +            details['Type'] = ndefTag.getType()
       +
       +            # check if tag is empty
       +            if not ndefMesg:
       +                details['Message'] = None
       +                return details
       +
       +            ndefrecords =  ndefMesg.getRecords()
       +            length = len(ndefrecords)
       +            #print 'length', length
       +            # will contain the NDEF record types
       +            recTypes = []
       +            for record in ndefrecords:
       +                recTypes.append({
       +                    'type': ''.join(map(unichr, record.getType())),
       +                    'payload': ''.join(map(unichr, record.getPayload()))
       +                    })
       +
       +            details['recTypes'] = recTypes
       +        except Exception as err:
       +            print(str(err))
       +
       +        return details
       +
       +    def on_new_intent(self, intent):
       +        ''' This function is called when the application receives a
       +        new intent, for the ones the application has registered previously,
       +        either in the manifest or in the foreground dispatch setup in the
       +        nfc_init function above. 
       +        '''
       +
       +        action_list = (NfcAdapter.ACTION_NDEF_DISCOVERED,)
       +        # get TAG
       +        #tag = cast('android.nfc.Tag', intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
       +
       +        #details = self.get_ndef_details(tag)
       +
       +        if intent.getAction() not in action_list:
       +            print('unknow action, avoid.')
       +            return
       +
       +        rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
       +        if not rawmsgs:
       +            return
       +        for message in rawmsgs:
       +            message = cast(NdefMessage, message)
       +            payload = message.getRecords()[0].getPayload()
       +            print('payload: {}'.format(''.join(map(chr, payload))))
       +
       +    def nfc_disable(self):
       +        '''Disable app from handling tags.
       +        '''
       +        self.disable_foreground_dispatch()
       +
       +    def nfc_enable(self):
       +        '''Enable app to handle tags when app in foreground.
       +        '''
       +        self.enable_foreground_dispatch()
       +
       +    def create_AAR(self):
       +        '''Create the record responsible for linking our application to the tag.
       +        '''
       +        return NdefRecord.createApplicationRecord(JString("org.electrum.kivy"))
       +
       +    def create_TNF_EXTERNAL(self, data):
       +        '''Create our actual payload record.
       +        '''
       +        if BUILDVERSION >= 14:
       +            domain = "org.electrum"
       +            stype = "externalType"
       +            extRecord = NdefRecord.createExternal(domain, stype, data)
       +        else:
       +            # Creating the NdefRecord manually:
       +            extRecord = NdefRecord(
       +                NdefRecord.TNF_EXTERNAL_TYPE,
       +                "org.electrum:externalType",
       +                '',
       +                data)
       +        return extRecord
       +
       +    def create_ndef_message(self, *recs):
       +        ''' Create the Ndef message that will be written to tag
       +        '''
       +        records = []
       +        for record in recs:
       +            if record:
       +                records.append(record)
       +
       +        return NdefMessage(records)
       +
       +
       +    @run_on_ui_thread
       +    def disable_foreground_dispatch(self):
       +        '''Disable foreground dispatch when app is paused.
       +        '''
       +        self.nfc_adapter.disableForegroundDispatch(self.j_context)
       +
       +    @run_on_ui_thread
       +    def enable_foreground_dispatch(self):
       +        '''Start listening for new tags
       +        '''
       +        self.nfc_adapter.enableForegroundDispatch(self.j_context,
       +                self.nfc_pending_intent, self.ndef_exchange_filters, self.ndef_tech_list)
       +
       +    @run_on_ui_thread
       +    def _nfc_enable_ndef_exchange(self, data):
       +        # Enable p2p exchange
       +        # Create record
       +        ndef_record = NdefRecord(
       +                NdefRecord.TNF_MIME_MEDIA,
       +                'org.electrum.kivy', '', data)
       +        
       +        # Create message
       +        ndef_message = NdefMessage([ndef_record])
       +
       +        # Enable ndef push
       +        self.nfc_adapter.enableForegroundNdefPush(self.j_context, ndef_message)
       +
       +        # Enable dispatch
       +        self.nfc_adapter.enableForegroundDispatch(self.j_context,
       +                self.nfc_pending_intent, self.ndef_exchange_filters, [])
       +
       +    @run_on_ui_thread
       +    def _nfc_disable_ndef_exchange(self):
       +        # Disable p2p exchange
       +        self.nfc_adapter.disableForegroundNdefPush(self.j_context)
       +        self.nfc_adapter.disableForegroundDispatch(self.j_context)
       +
       +    def nfc_enable_exchange(self, data):
       +        '''Enable Ndef exchange for p2p
       +        '''
       +        self._nfc_enable_ndef_exchange()
       +
       +    def nfc_disable_exchange(self):
       +        ''' Disable Ndef exchange for p2p
       +        '''
       +        self._nfc_disable_ndef_exchange()
   DIR diff --git a/electrum/gui/kivy/nfc_scanner/scanner_dummy.py b/electrum/gui/kivy/nfc_scanner/scanner_dummy.py
       t@@ -0,0 +1,52 @@
       +''' Dummy NFC Provider to be used on desktops in case no other provider is found
       +'''
       +from . import NFCBase
       +from kivy.clock import Clock
       +from kivy.logger import Logger
       +
       +class ScannerDummy(NFCBase):
       +    '''This is the dummy interface that gets selected in case any other
       +    hardware interface to NFC is not available.
       +    '''
       +
       +    _initialised = False
       +
       +    name = 'NFCDummy'
       +
       +    def nfc_init(self):
       +        # print 'nfc_init()'
       +
       +        Logger.debug('NFC: configure nfc')
       +        self._initialised = True
       +        self.nfc_enable()
       +        return True
       +
       +    def on_new_intent(self, dt):
       +        tag_info = {'type': 'dymmy',
       +                    'message': 'dummy',
       +                    'extra details': None}
       +
       +        # let Main app know that a tag has been detected
       +        app = App.get_running_app()
       +        app.tag_discovered(tag_info)
       +        app.show_info('New tag detected.', duration=2)
       +        Logger.debug('NFC: got new dummy tag')
       +
       +    def nfc_enable(self):
       +        Logger.debug('NFC: enable')
       +        if self._initialised:
       +            Clock.schedule_interval(self.on_new_intent, 22)
       +
       +    def nfc_disable(self):
       +        # print 'nfc_enable()'
       +        Clock.unschedule(self.on_new_intent)
       +
       +    def nfc_enable_exchange(self, data):
       +        ''' Start sending data
       +        '''
       +        Logger.debug('NFC: sending data {}'.format(data))
       +
       +    def nfc_disable_exchange(self):
       +        ''' Disable/Stop ndef exchange
       +        '''
       +        Logger.debug('NFC: disable nfc exchange')
   DIR diff --git a/gui/kivy/theming/light/action_bar.png b/electrum/gui/kivy/theming/light/action_bar.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/action_button_group.png b/electrum/gui/kivy/theming/light/action_button_group.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/action_group_dark.png b/electrum/gui/kivy/theming/light/action_group_dark.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/action_group_light.png b/electrum/gui/kivy/theming/light/action_group_light.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/add_contact.png b/electrum/gui/kivy/theming/light/add_contact.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/arrow_back.png b/electrum/gui/kivy/theming/light/arrow_back.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/bit_logo.png b/electrum/gui/kivy/theming/light/bit_logo.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/blue_bg_round_rb.png b/electrum/gui/kivy/theming/light/blue_bg_round_rb.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/btn_create_account.png b/electrum/gui/kivy/theming/light/btn_create_account.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/btn_create_act_disabled.png b/electrum/gui/kivy/theming/light/btn_create_act_disabled.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/btn_nfc.png b/electrum/gui/kivy/theming/light/btn_nfc.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/btn_send_address.png b/electrum/gui/kivy/theming/light/btn_send_address.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/btn_send_nfc.png b/electrum/gui/kivy/theming/light/btn_send_nfc.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/calculator.png b/electrum/gui/kivy/theming/light/calculator.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/camera.png b/electrum/gui/kivy/theming/light/camera.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/card.png b/electrum/gui/kivy/theming/light/card.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/card_bottom.png b/electrum/gui/kivy/theming/light/card_bottom.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/card_btn.png b/electrum/gui/kivy/theming/light/card_btn.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/card_top.png b/electrum/gui/kivy/theming/light/card_top.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/carousel_deselected.png b/electrum/gui/kivy/theming/light/carousel_deselected.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/carousel_selected.png b/electrum/gui/kivy/theming/light/carousel_selected.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/clock1.png b/electrum/gui/kivy/theming/light/clock1.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/clock2.png b/electrum/gui/kivy/theming/light/clock2.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/clock3.png b/electrum/gui/kivy/theming/light/clock3.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/clock4.png b/electrum/gui/kivy/theming/light/clock4.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/clock5.png b/electrum/gui/kivy/theming/light/clock5.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/close.png b/electrum/gui/kivy/theming/light/close.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/closebutton.png b/electrum/gui/kivy/theming/light/closebutton.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/confirmed.png b/electrum/gui/kivy/theming/light/confirmed.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/contact.png b/electrum/gui/kivy/theming/light/contact.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/contact_overlay.png b/electrum/gui/kivy/theming/light/contact_overlay.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/create_act_text.png b/electrum/gui/kivy/theming/light/create_act_text.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/create_act_text_active.png b/electrum/gui/kivy/theming/light/create_act_text_active.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/dialog.png b/electrum/gui/kivy/theming/light/dialog.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/dropdown_background.png b/electrum/gui/kivy/theming/light/dropdown_background.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/electrum_icon640.png b/electrum/gui/kivy/theming/light/electrum_icon640.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/error.png b/electrum/gui/kivy/theming/light/error.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/gear.png b/electrum/gui/kivy/theming/light/gear.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/globe.png b/electrum/gui/kivy/theming/light/globe.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/icon_border.png b/electrum/gui/kivy/theming/light/icon_border.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/important.png b/electrum/gui/kivy/theming/light/important.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/info.png b/electrum/gui/kivy/theming/light/info.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/lightblue_bg_round_lb.png b/electrum/gui/kivy/theming/light/lightblue_bg_round_lb.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/logo.png b/electrum/gui/kivy/theming/light/logo.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/logo_atom_dull.png b/electrum/gui/kivy/theming/light/logo_atom_dull.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/mail_icon.png b/electrum/gui/kivy/theming/light/mail_icon.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/manualentry.png b/electrum/gui/kivy/theming/light/manualentry.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/network.png b/electrum/gui/kivy/theming/light/network.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/nfc.png b/electrum/gui/kivy/theming/light/nfc.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/nfc_clock.png b/electrum/gui/kivy/theming/light/nfc_clock.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/nfc_phone.png b/electrum/gui/kivy/theming/light/nfc_phone.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/nfc_stage_one.png b/electrum/gui/kivy/theming/light/nfc_stage_one.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/overflow_background.png b/electrum/gui/kivy/theming/light/overflow_background.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/overflow_btn_dn.png b/electrum/gui/kivy/theming/light/overflow_btn_dn.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/paste_icon.png b/electrum/gui/kivy/theming/light/paste_icon.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/pen.png b/electrum/gui/kivy/theming/light/pen.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/qrcode.png b/electrum/gui/kivy/theming/light/qrcode.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/save.png b/electrum/gui/kivy/theming/light/save.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/settings.png b/electrum/gui/kivy/theming/light/settings.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/shadow.png b/electrum/gui/kivy/theming/light/shadow.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/shadow_right.png b/electrum/gui/kivy/theming/light/shadow_right.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/share.png b/electrum/gui/kivy/theming/light/share.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/star_big_inactive.png b/electrum/gui/kivy/theming/light/star_big_inactive.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/stepper_full.png b/electrum/gui/kivy/theming/light/stepper_full.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/stepper_left.png b/electrum/gui/kivy/theming/light/stepper_left.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/stepper_restore_password.png b/electrum/gui/kivy/theming/light/stepper_restore_password.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/stepper_restore_seed.png b/electrum/gui/kivy/theming/light/stepper_restore_seed.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/tab.png b/electrum/gui/kivy/theming/light/tab.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/tab_btn.png b/electrum/gui/kivy/theming/light/tab_btn.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/tab_btn_disabled.png b/electrum/gui/kivy/theming/light/tab_btn_disabled.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/tab_btn_pressed.png b/electrum/gui/kivy/theming/light/tab_btn_pressed.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/tab_disabled.png b/electrum/gui/kivy/theming/light/tab_disabled.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/tab_strip.png b/electrum/gui/kivy/theming/light/tab_strip.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/textinput_active.png b/electrum/gui/kivy/theming/light/textinput_active.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/unconfirmed.png b/electrum/gui/kivy/theming/light/unconfirmed.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/wallet.png b/electrum/gui/kivy/theming/light/wallet.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/wallets.png b/electrum/gui/kivy/theming/light/wallets.png
       Binary files differ.
   DIR diff --git a/gui/kivy/theming/light/white_bg_round_top.png b/electrum/gui/kivy/theming/light/white_bg_round_top.png
       Binary files differ.
   DIR diff --git a/gui/kivy/tools/bitcoin_intent.xml b/electrum/gui/kivy/tools/bitcoin_intent.xml
   DIR diff --git a/gui/kivy/tools/blacklist.txt b/electrum/gui/kivy/tools/blacklist.txt
   DIR diff --git a/gui/kivy/tools/buildozer.spec b/electrum/gui/kivy/tools/buildozer.spec
   DIR diff --git a/gui/kivy/uix/__init__.py b/electrum/gui/kivy/uix/__init__.py
   DIR diff --git a/gui/kivy/uix/combobox.py b/electrum/gui/kivy/uix/combobox.py
   DIR diff --git a/electrum/gui/kivy/uix/context_menu.py b/electrum/gui/kivy/uix/context_menu.py
       t@@ -0,0 +1,56 @@
       +#!python
       +#!/usr/bin/env python
       +from kivy.app import App
       +from kivy.uix.bubble import Bubble
       +from kivy.animation import Animation
       +from kivy.uix.floatlayout import FloatLayout
       +from kivy.lang import Builder
       +from kivy.factory import Factory
       +from kivy.clock import Clock
       +
       +from electrum.gui.kivy.i18n import _
       +
       +Builder.load_string('''
       +<MenuItem@Button>
       +    background_normal: ''
       +    background_color: (0.192, .498, 0.745, 1)
       +    height: '48dp'
       +    size_hint: 1, None
       +
       +<ContextMenu>
       +    size_hint: 1, None
       +    height: '48dp'
       +    pos: (0, 0)
       +    show_arrow: False
       +    arrow_pos: 'top_mid'
       +    padding: 0
       +    orientation: 'horizontal'
       +    BoxLayout:
       +        size_hint: 1, 1
       +        height: '48dp'
       +        padding: '12dp', '0dp'
       +        spacing: '3dp'
       +        orientation: 'horizontal'
       +        id: buttons
       +''')
       +
       +
       +class MenuItem(Factory.Button):
       +    pass
       +
       +class ContextMenu(Bubble):
       +
       +    def __init__(self, obj, action_list):
       +        Bubble.__init__(self)
       +        self.obj = obj
       +        for k, v in action_list:
       +            l = MenuItem()
       +            l.text = _(k)
       +            def func(f=v):
       +                Clock.schedule_once(lambda dt: f(obj), 0.15)
       +            l.on_release = func
       +            self.ids.buttons.add_widget(l)
       +
       +    def hide(self):
       +        if self.parent:
       +            self.parent.hide_menu()
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/__init__.py b/electrum/gui/kivy/uix/dialogs/__init__.py
       t@@ -0,0 +1,220 @@
       +from kivy.app import App
       +from kivy.clock import Clock
       +from kivy.factory import Factory
       +from kivy.properties import NumericProperty, StringProperty, BooleanProperty
       +from kivy.core.window import Window
       +from kivy.uix.recycleview import RecycleView
       +from kivy.uix.boxlayout import BoxLayout
       +
       +from electrum.gui.kivy.i18n import _
       +
       +
       +
       +class AnimatedPopup(Factory.Popup):
       +    ''' An Animated Popup that animates in and out.
       +    '''
       +
       +    anim_duration = NumericProperty(.36)
       +    '''Duration of animation to be used
       +    '''
       +
       +    __events__ = ['on_activate', 'on_deactivate']
       +
       +
       +    def on_activate(self):
       +        '''Base function to be overridden on inherited classes.
       +        Called when the popup is done animating.
       +        '''
       +        pass
       +
       +    def on_deactivate(self):
       +        '''Base function to be overridden on inherited classes.
       +        Called when the popup is done animating.
       +        '''
       +        pass
       +
       +    def open(self):
       +        '''Do the initialization of incoming animation here.
       +        Override to set your custom animation.
       +        '''
       +        def on_complete(*l):
       +            self.dispatch('on_activate')
       +
       +        self.opacity = 0
       +        super(AnimatedPopup, self).open()
       +        anim = Factory.Animation(opacity=1, d=self.anim_duration)
       +        anim.bind(on_complete=on_complete)
       +        anim.start(self)
       +
       +    def dismiss(self):
       +        '''Do the initialization of incoming animation here.
       +        Override to set your custom animation.
       +        '''
       +        def on_complete(*l):
       +            super(AnimatedPopup, self).dismiss()
       +            self.dispatch('on_deactivate')
       +
       +        anim = Factory.Animation(opacity=0, d=.25)
       +        anim.bind(on_complete=on_complete)
       +        anim.start(self)
       +
       +class EventsDialog(Factory.Popup):
       +    ''' Abstract Popup that provides the following events
       +    .. events::
       +        `on_release`
       +        `on_press`
       +    '''
       +
       +    __events__ = ('on_release', 'on_press')
       +
       +    def __init__(self, **kwargs):
       +        super(EventsDialog, self).__init__(**kwargs)
       +
       +    def on_release(self, instance):
       +        pass
       +
       +    def on_press(self, instance):
       +        pass
       +
       +    def close(self):
       +        self.dismiss()
       +
       +
       +class SelectionDialog(EventsDialog):
       +
       +    def add_widget(self, widget, index=0):
       +        if self.content:
       +            self.content.add_widget(widget, index)
       +            return
       +        super(SelectionDialog, self).add_widget(widget)
       +
       +
       +class InfoBubble(Factory.Bubble):
       +    '''Bubble to be used to display short Help Information'''
       +
       +    message = StringProperty(_('Nothing set !'))
       +    '''Message to be displayed; defaults to "nothing set"'''
       +
       +    icon = StringProperty('')
       +    ''' Icon to be displayed along with the message defaults to ''
       +
       +    :attr:`icon` is a  `StringProperty` defaults to `''`
       +    '''
       +
       +    fs = BooleanProperty(False)
       +    ''' Show Bubble in half screen mode
       +
       +    :attr:`fs` is a `BooleanProperty` defaults to `False`
       +    '''
       +
       +    modal = BooleanProperty(False)
       +    ''' Allow bubble to be hidden on touch.
       +
       +    :attr:`modal` is a `BooleanProperty` defauult to `False`.
       +    '''
       +
       +    exit = BooleanProperty(False)
       +    '''Indicates whether to exit app after bubble is closed.
       +
       +    :attr:`exit` is a `BooleanProperty` defaults to False.
       +    '''
       +
       +    dim_background = BooleanProperty(False)
       +    ''' Indicates Whether to draw a background on the windows behind the bubble.
       +
       +    :attr:`dim` is a `BooleanProperty` defaults to `False`.
       +    '''
       +
       +    def on_touch_down(self, touch):
       +        if self.modal:
       +            return True
       +        self.hide()
       +        if self.collide_point(*touch.pos):
       +            return True
       +
       +    def show(self, pos, duration, width=None, modal=False, exit=False):
       +        '''Animate the bubble into position'''
       +        self.modal, self.exit = modal, exit
       +        if width:
       +            self.width = width
       +        if self.modal:
       +            from kivy.uix.modalview import ModalView
       +            self._modal_view = m = ModalView(background_color=[.5, .5, .5, .2])
       +            Window.add_widget(m)
       +            m.add_widget(self)
       +        else:
       +            Window.add_widget(self)
       +
       +        # wait for the bubble to adjust its size according to text then animate
       +        Clock.schedule_once(lambda dt: self._show(pos, duration))
       +
       +    def _show(self, pos, duration):
       +
       +        def on_stop(*l):
       +            if duration:
       +                Clock.schedule_once(self.hide, duration + .5)
       +
       +        self.opacity = 0
       +        arrow_pos = self.arrow_pos
       +        if arrow_pos[0] in ('l', 'r'):
       +            pos = pos[0], pos[1] - (self.height/2)
       +        else:
       +            pos = pos[0] - (self.width/2), pos[1]
       +
       +        self.limit_to = Window
       +
       +        anim = Factory.Animation(opacity=1, pos=pos, d=.32)
       +        anim.bind(on_complete=on_stop)
       +        anim.cancel_all(self)
       +        anim.start(self)
       +
       +
       +    def hide(self, now=False):
       +        ''' Auto fade out the Bubble
       +        '''
       +        def on_stop(*l):
       +            if self.modal:
       +                m = self._modal_view
       +                m.remove_widget(self)
       +                Window.remove_widget(m)
       +            Window.remove_widget(self)
       +            if self.exit:
       +                App.get_running_app().stop()
       +                import sys
       +                sys.exit()
       +            else:
       +                App.get_running_app().is_exit = False
       +
       +        if now:
       +            return on_stop()
       +
       +        anim = Factory.Animation(opacity=0, d=.25)
       +        anim.bind(on_complete=on_stop)
       +        anim.cancel_all(self)
       +        anim.start(self)
       +
       +
       +
       +class OutputItem(BoxLayout):
       +    pass
       +
       +class OutputList(RecycleView):
       +
       +    def __init__(self, **kwargs):
       +        super(OutputList, self).__init__(**kwargs)
       +        self.app = App.get_running_app()
       +
       +    def update(self, outputs):
       +        res = []
       +        for (type, address, amount) in outputs:
       +            value = self.app.format_amount_and_units(amount)
       +            res.append({'address': address, 'value': value})
       +        self.data = res
       +
       +
       +class TopLabel(Factory.Label):
       +    pass
       +
       +
       +class RefLabel(TopLabel):
       +    pass
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/addresses.py b/electrum/gui/kivy/uix/dialogs/addresses.py
       t@@ -0,0 +1,180 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +from decimal import Decimal
       +
       +Builder.load_string('''
       +<AddressLabel@Label>
       +    text_size: self.width, None
       +    halign: 'left'
       +    valign: 'top'
       +
       +<AddressItem@CardItem>
       +    address: ''
       +    memo: ''
       +    amount: ''
       +    status: ''
       +    BoxLayout:
       +        spacing: '8dp'
       +        height: '32dp'
       +        orientation: 'vertical'
       +        Widget
       +        AddressLabel:
       +            text: root.address
       +            shorten: True
       +        Widget
       +        AddressLabel:
       +            text: (root.amount if root.status == 'Funded' else root.status) + '     ' + root.memo
       +            color: .699, .699, .699, 1
       +            font_size: '13sp'
       +            shorten: True
       +        Widget
       +
       +<AddressesDialog@Popup>
       +    id: popup
       +    title: _('Addresses')
       +    message: ''
       +    pr_status: 'Pending'
       +    show_change: 0
       +    show_used: 0
       +    on_message:
       +        self.update()
       +    BoxLayout:
       +        id:box
       +        padding: '12dp', '70dp', '12dp', '12dp'
       +        spacing: '12dp'
       +        orientation: 'vertical'
       +        size_hint: 1, 1.1
       +        BoxLayout:
       +            spacing: '6dp'
       +            size_hint: 1, None
       +            orientation: 'horizontal'
       +            AddressFilter:
       +                opacity: 1
       +                size_hint: 1, None
       +                height: self.minimum_height
       +                spacing: '5dp'
       +                AddressButton:
       +                    id: search
       +                    text: {0:_('Receiving'), 1:_('Change'), 2:_('All')}[root.show_change]
       +                    on_release:
       +                        root.show_change = (root.show_change + 1) % 3
       +                        Clock.schedule_once(lambda dt: root.update())
       +            AddressFilter:
       +                opacity: 1
       +                size_hint: 1, None
       +                height: self.minimum_height
       +                spacing: '5dp'
       +                AddressButton:
       +                    id: search
       +                    text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used')}[root.show_used]
       +                    on_release:
       +                        root.show_used = (root.show_used + 1) % 4
       +                        Clock.schedule_once(lambda dt: root.update())
       +            AddressFilter:
       +                opacity: 1
       +                size_hint: 1, None
       +                height: self.minimum_height
       +                spacing: '5dp'
       +                canvas.before:
       +                    Color:
       +                        rgba: 0.9, 0.9, 0.9, 1
       +                AddressButton:
       +                    id: change
       +                    text: root.message if root.message else _('Search')
       +                    on_release: Clock.schedule_once(lambda dt: app.description_dialog(popup))
       +        RecycleView:
       +            scroll_type: ['bars', 'content']
       +            bar_width: '15dp'
       +            viewclass: 'AddressItem'
       +            id: search_container
       +            RecycleBoxLayout:
       +                orientation: 'vertical'
       +                default_size: None, dp(56)
       +                default_size_hint: 1, None
       +                size_hint_y: None
       +                height: self.minimum_height
       +''')
       +
       +
       +from electrum.gui.kivy.i18n import _
       +from electrum.gui.kivy.uix.context_menu import ContextMenu
       +
       +
       +class AddressesDialog(Factory.Popup):
       +
       +    def __init__(self, app, screen, callback):
       +        Factory.Popup.__init__(self)
       +        self.app = app
       +        self.screen = screen
       +        self.callback = callback
       +        self.context_menu = None
       +
       +    def get_card(self, addr, balance, is_used, label):
       +        ci = {}
       +        ci['screen'] = self
       +        ci['address'] = addr
       +        ci['memo'] = label
       +        ci['amount'] = self.app.format_amount_and_units(balance)
       +        ci['status'] = _('Used') if is_used else _('Funded') if balance > 0 else _('Unused')
       +        return ci
       +
       +    def update(self):
       +        self.menu_actions = [(_('Use'), self.do_use), (_('Details'), self.do_view)]
       +        wallet = self.app.wallet
       +        if self.show_change == 0:
       +            _list = wallet.get_receiving_addresses()
       +        elif self.show_change == 1:
       +            _list = wallet.get_change_addresses()
       +        else:
       +            _list = wallet.get_addresses()
       +        search = self.message
       +        container = self.ids.search_container
       +        n = 0
       +        cards = []
       +        for address in _list:
       +            label = wallet.labels.get(address, '')
       +            balance = sum(wallet.get_addr_balance(address))
       +            is_used = wallet.is_used(address)
       +            if self.show_used == 1 and (balance or is_used):
       +                continue
       +            if self.show_used == 2 and balance == 0:
       +                continue
       +            if self.show_used == 3 and not is_used:
       +                continue
       +            card = self.get_card(address, balance, is_used, label)
       +            if search and not self.ext_search(card, search):
       +                continue
       +            cards.append(card)
       +            n += 1
       +        container.data = cards
       +        if not n:
       +            self.app.show_error('No address matching your search')
       +
       +    def do_use(self, obj):
       +        self.hide_menu()
       +        self.dismiss()
       +        self.app.show_request(obj.address)
       +
       +    def do_view(self, obj):
       +        req = { 'address': obj.address, 'status' : obj.status }
       +        status = obj.status
       +        c, u, x = self.app.wallet.get_addr_balance(obj.address)
       +        balance = c + u + x
       +        if balance > 0:
       +            req['fund'] = balance
       +        self.app.show_addr_details(req, status)
       +
       +    def ext_search(self, card, search):
       +        return card['memo'].find(search) >= 0 or card['amount'].find(search) >= 0
       +
       +    def show_menu(self, obj):
       +        self.hide_menu()
       +        self.context_menu = ContextMenu(obj, self.menu_actions)
       +        self.ids.box.add_widget(self.context_menu)
       +
       +    def hide_menu(self):
       +        if self.context_menu is not None:
       +            self.ids.box.remove_widget(self.context_menu)
       +            self.context_menu = None
   DIR diff --git a/gui/kivy/uix/dialogs/amount_dialog.py b/electrum/gui/kivy/uix/dialogs/amount_dialog.py
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/bump_fee_dialog.py b/electrum/gui/kivy/uix/dialogs/bump_fee_dialog.py
       t@@ -0,0 +1,118 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +
       +from electrum.gui.kivy.i18n import _
       +
       +Builder.load_string('''
       +<BumpFeeDialog@Popup>
       +    title: _('Bump fee')
       +    size_hint: 0.8, 0.8
       +    pos_hint: {'top':0.9}
       +    BoxLayout:
       +        orientation: 'vertical'
       +        padding: '10dp'
       +
       +        GridLayout:
       +            height: self.minimum_height
       +            size_hint_y: None
       +            cols: 1
       +            spacing: '10dp'
       +            BoxLabel:
       +                id: old_fee
       +                text: _('Current Fee')
       +                value: ''
       +            BoxLabel:
       +                id: new_fee
       +                text: _('New Fee')
       +                value: ''
       +        Label:
       +            id: tooltip1
       +            text: ''
       +            size_hint_y: None
       +        Label:
       +            id: tooltip2
       +            text: ''
       +            size_hint_y: None
       +        Slider:
       +            id: slider
       +            range: 0, 4
       +            step: 1
       +            on_value: root.on_slider(self.value)
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.2
       +            Label:
       +                text: _('Final')
       +            CheckBox:
       +                id: final_cb
       +        Widget:
       +            size_hint: 1, 1
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.5
       +            Button:
       +                text: 'Cancel'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release: root.dismiss()
       +            Button:
       +                text: 'OK'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release:
       +                    root.dismiss()
       +                    root.on_ok()
       +''')
       +
       +class BumpFeeDialog(Factory.Popup):
       +
       +    def __init__(self, app, fee, size, callback):
       +        Factory.Popup.__init__(self)
       +        self.app = app
       +        self.init_fee = fee
       +        self.tx_size = size
       +        self.callback = callback
       +        self.config = app.electrum_config
       +        self.mempool = self.config.use_mempool_fees()
       +        self.dynfees = self.config.is_dynfee() and bool(self.app.network) and self.config.has_dynamic_fees_ready()
       +        self.ids.old_fee.value = self.app.format_amount_and_units(self.init_fee)
       +        self.update_slider()
       +        self.update_text()
       +
       +    def update_text(self):
       +        fee = self.get_fee()
       +        self.ids.new_fee.value = self.app.format_amount_and_units(fee)
       +        pos = int(self.ids.slider.value)
       +        fee_rate = self.get_fee_rate()
       +        text, tooltip = self.config.get_fee_text(pos, self.dynfees, self.mempool, fee_rate)
       +        self.ids.tooltip1.text = text
       +        self.ids.tooltip2.text = tooltip
       +
       +    def update_slider(self):
       +        slider = self.ids.slider
       +        maxp, pos, fee_rate = self.config.get_fee_slider(self.dynfees, self.mempool)
       +        slider.range = (0, maxp)
       +        slider.step = 1
       +        slider.value = pos
       +
       +    def get_fee_rate(self):
       +        pos = int(self.ids.slider.value)
       +        if self.dynfees:
       +            fee_rate = self.config.depth_to_fee(pos) if self.mempool else self.config.eta_to_fee(pos)
       +        else:
       +            fee_rate = self.config.static_fee(pos)
       +        return fee_rate
       +
       +    def get_fee(self):
       +        fee_rate = self.get_fee_rate()
       +        return int(fee_rate * self.tx_size // 1000)
       +
       +    def on_ok(self):
       +        new_fee = self.get_fee()
       +        is_final = self.ids.final_cb.active
       +        self.callback(self.init_fee, new_fee, is_final)
       +
       +    def on_slider(self, value):
       +        self.update_text()
   DIR diff --git a/gui/kivy/uix/dialogs/checkbox_dialog.py b/electrum/gui/kivy/uix/dialogs/checkbox_dialog.py
   DIR diff --git a/gui/kivy/uix/dialogs/choice_dialog.py b/electrum/gui/kivy/uix/dialogs/choice_dialog.py
   DIR diff --git a/gui/kivy/uix/dialogs/crash_reporter.py b/electrum/gui/kivy/uix/dialogs/crash_reporter.py
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/fee_dialog.py b/electrum/gui/kivy/uix/dialogs/fee_dialog.py
       t@@ -0,0 +1,131 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +
       +from electrum.gui.kivy.i18n import _
       +
       +Builder.load_string('''
       +<FeeDialog@Popup>
       +    id: popup
       +    title: _('Transaction Fees')
       +    size_hint: 0.8, 0.8
       +    pos_hint: {'top':0.9}
       +    method: 0
       +    BoxLayout:
       +        orientation: 'vertical'
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.5
       +            Label:
       +                text: _('Method') + ':'
       +            Button:
       +                text: _('Mempool') if root.method == 2 else _('ETA') if root.method == 1 else _('Static')
       +                background_color: (0,0,0,0)
       +                bold: True
       +                on_release:
       +                    root.method  = (root.method + 1) % 3
       +                    root.update_slider()
       +                    root.update_text()
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.5
       +            Label:
       +                text: (_('Target') if root.method > 0 else _('Fee')) + ':'
       +            Label:
       +                id: fee_target
       +                text: ''
       +        Slider:
       +            id: slider
       +            range: 0, 4
       +            step: 1
       +            on_value: root.on_slider(self.value)
       +        Widget:
       +            size_hint: 1, 0.5
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.5
       +            TopLabel:
       +                id: fee_estimate
       +                text: ''
       +                font_size: '14dp'
       +        Widget:
       +            size_hint: 1, 0.5
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.5
       +            Button:
       +                text: 'Cancel'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release: popup.dismiss()
       +            Button:
       +                text: 'OK'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release:
       +                    root.on_ok()
       +                    root.dismiss()
       +''')
       +
       +class FeeDialog(Factory.Popup):
       +
       +    def __init__(self, app, config, callback):
       +        Factory.Popup.__init__(self)
       +        self.app = app
       +        self.config = config
       +        self.callback = callback
       +        mempool = self.config.use_mempool_fees()
       +        dynfees = self.config.is_dynfee()
       +        self.method = (2 if mempool else 1) if dynfees else 0
       +        self.update_slider()
       +        self.update_text()
       +
       +    def update_text(self):
       +        pos = int(self.ids.slider.value)
       +        dynfees, mempool = self.get_method()
       +        if self.method == 2:
       +            fee_rate = self.config.depth_to_fee(pos)
       +            target, estimate = self.config.get_fee_text(pos, dynfees, mempool, fee_rate)
       +            msg = 'In the current network conditions, in order to be positioned %s, a transaction will require a fee of %s.' % (target, estimate)
       +        elif self.method == 1:
       +            fee_rate = self.config.eta_to_fee(pos)
       +            target, estimate = self.config.get_fee_text(pos, dynfees, mempool, fee_rate)
       +            msg = 'In the last few days, transactions that confirmed %s usually paid a fee of at least %s.' % (target.lower(), estimate)
       +        else:
       +            fee_rate = self.config.static_fee(pos)
       +            target, estimate = self.config.get_fee_text(pos, dynfees, True, fee_rate)
       +            msg = 'In the current network conditions, a transaction paying %s would be positioned %s.' % (target, estimate)
       +
       +        self.ids.fee_target.text = target
       +        self.ids.fee_estimate.text = msg
       +
       +    def get_method(self):
       +        dynfees = self.method > 0
       +        mempool = self.method == 2
       +        return dynfees, mempool
       +
       +    def update_slider(self):
       +        slider = self.ids.slider
       +        dynfees, mempool = self.get_method()
       +        maxp, pos, fee_rate = self.config.get_fee_slider(dynfees, mempool)
       +        slider.range = (0, maxp)
       +        slider.step = 1
       +        slider.value = pos
       +
       +    def on_ok(self):
       +        value = int(self.ids.slider.value)
       +        dynfees, mempool = self.get_method()
       +        self.config.set_key('dynamic_fees', dynfees, False)
       +        self.config.set_key('mempool_fees', mempool, False)
       +        if dynfees:
       +            if mempool:
       +                self.config.set_key('depth_level', value, True)
       +            else:
       +                self.config.set_key('fee_level', value, True)
       +        else:
       +            self.config.set_key('fee_per_kb', self.config.static_fee(value), True)
       +        self.callback()
       +
       +    def on_slider(self, value):
       +        self.update_text()
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/fx_dialog.py b/electrum/gui/kivy/uix/dialogs/fx_dialog.py
       t@@ -0,0 +1,111 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +
       +Builder.load_string('''
       +<FxDialog@Popup>
       +    id: popup
       +    title: 'Fiat Currency'
       +    size_hint: 0.8, 0.8
       +    pos_hint: {'top':0.9}
       +    BoxLayout:
       +        orientation: 'vertical'
       +
       +        Widget:
       +            size_hint: 1, 0.1
       +
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.1
       +            Label:
       +                text: _('Currency')
       +                height: '48dp'
       +            Spinner:
       +                height: '48dp'
       +                id: ccy
       +                on_text: popup.on_currency(self.text)
       +
       +        Widget:
       +            size_hint: 1, 0.1
       +
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.1
       +            Label:
       +                text: _('Source')
       +                height: '48dp'
       +            Spinner:
       +                height: '48dp'
       +                id: exchanges
       +                on_text: popup.on_exchange(self.text)
       +
       +        Widget:
       +            size_hint: 1, 0.2
       +
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.2
       +            Button:
       +                text: 'Cancel'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release: popup.dismiss()
       +            Button:
       +                text: 'OK'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release:
       +                    root.callback()
       +                    popup.dismiss()
       +''')
       +
       +
       +from kivy.uix.label import Label
       +from kivy.uix.checkbox import CheckBox
       +from kivy.uix.widget import Widget
       +from kivy.clock import Clock
       +
       +from electrum.gui.kivy.i18n import _
       +from functools import partial
       +
       +class FxDialog(Factory.Popup):
       +
       +    def __init__(self, app, plugins, config, callback):
       +        Factory.Popup.__init__(self)
       +        self.app = app
       +        self.config = config
       +        self.callback = callback
       +        self.fx = self.app.fx
       +        self.fx.set_history_config(True)
       +        self.add_currencies()
       +
       +    def add_exchanges(self):
       +        exchanges = sorted(self.fx.get_exchanges_by_ccy(self.fx.get_currency(), True)) if self.fx.is_enabled() else []
       +        mx = self.fx.exchange.name() if self.fx.is_enabled() else ''
       +        ex = self.ids.exchanges
       +        ex.values = exchanges
       +        ex.text = (mx if mx in exchanges else exchanges[0]) if self.fx.is_enabled() else ''
       +
       +    def on_exchange(self, text):
       +        if not text:
       +            return
       +        if self.fx.is_enabled() and text != self.fx.exchange.name():
       +            self.fx.set_exchange(text)
       +
       +    def add_currencies(self):
       +        currencies = [_('None')] + self.fx.get_currencies(True)
       +        my_ccy = self.fx.get_currency() if self.fx.is_enabled() else _('None')
       +        self.ids.ccy.values = currencies
       +        self.ids.ccy.text = my_ccy
       +
       +    def on_currency(self, ccy):
       +        b = (ccy != _('None'))
       +        self.fx.set_enabled(b)
       +        if b:
       +            if ccy != self.fx.get_currency():
       +                self.fx.set_currency(ccy)
       +            self.app.fiat_unit = ccy
       +        else:
       +            self.app.is_fiat = False
       +        Clock.schedule_once(lambda dt: self.add_exchanges())
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py
       t@@ -0,0 +1,1038 @@
       +
       +from functools import partial
       +import threading
       +import os
       +
       +from kivy.app import App
       +from kivy.clock import Clock
       +from kivy.lang import Builder
       +from kivy.properties import ObjectProperty, StringProperty, OptionProperty
       +from kivy.core.window import Window
       +from kivy.uix.button import Button
       +from kivy.utils import platform
       +from kivy.uix.widget import Widget
       +from kivy.core.window import Window
       +from kivy.clock import Clock
       +from kivy.utils import platform
       +
       +from electrum.base_wizard import BaseWizard
       +from electrum.util import is_valid_email
       +
       +
       +from . import EventsDialog
       +from ...i18n import _
       +from .password_dialog import PasswordDialog
       +
       +# global Variables
       +is_test = (platform == "linux")
       +test_seed = "time taxi field recycle tiny license olive virus report rare steel portion achieve"
       +test_seed = "grape impose jazz bind spatial mind jelly tourist tank today holiday stomach"
       +test_xpub = "xpub661MyMwAqRbcEbvVtRRSjqxVnaWVUMewVzMiURAKyYratih4TtBpMypzzefmv8zUNebmNVzB3PojdC5sV2P9bDgMoo9B3SARw1MXUUfU1GL"
       +
       +Builder.load_string('''
       +#:import Window kivy.core.window.Window
       +#:import _ electrum.gui.kivy.i18n._
       +
       +
       +<WizardTextInput@TextInput>
       +    border: 4, 4, 4, 4
       +    font_size: '15sp'
       +    padding: '15dp', '15dp'
       +    background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
       +    foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
       +    hint_text_color: self.foreground_color
       +    background_active: 'atlas://electrum/gui/kivy/theming/light/create_act_text_active'
       +    background_normal: 'atlas://electrum/gui/kivy/theming/light/create_act_text_active'
       +    size_hint_y: None
       +    height: '48sp'
       +
       +<WizardButton@Button>:
       +    root: None
       +    size_hint: 1, None
       +    height: '48sp'
       +    on_press: if self.root: self.root.dispatch('on_press', self)
       +    on_release: if self.root: self.root.dispatch('on_release', self)
       +
       +<BigLabel@Label>
       +    color: .854, .925, .984, 1
       +    size_hint: 1, None
       +    text_size: self.width, None
       +    height: self.texture_size[1]
       +    bold: True
       +
       +<-WizardDialog>
       +    text_color: .854, .925, .984, 1
       +    value: ''
       +    #auto_dismiss: False
       +    size_hint: None, None
       +    canvas.before:
       +        Color:
       +            rgba: .239, .588, .882, 1
       +        Rectangle:
       +            size: Window.size
       +
       +    crcontent: crcontent
       +    # add electrum icon
       +    BoxLayout:
       +        orientation: 'vertical' if self.width < self.height else 'horizontal'
       +        padding:
       +            min(dp(27), self.width/32), min(dp(27), self.height/32),\
       +            min(dp(27), self.width/32), min(dp(27), self.height/32)
       +        spacing: '10dp'
       +        GridLayout:
       +            id: grid_logo
       +            cols: 1
       +            pos_hint: {'center_y': .5}
       +            size_hint: 1, None
       +            height: self.minimum_height
       +            Label:
       +                color: root.text_color
       +                text: 'ELECTRUM'
       +                size_hint: 1, None
       +                height: self.texture_size[1] if self.opacity else 0
       +                font_size: '33sp'
       +                font_name: 'electrum/gui/kivy/data/fonts/tron/Tr2n.ttf'
       +        GridLayout:
       +            cols: 1
       +            id: crcontent
       +            spacing: '1dp'
       +        Widget:
       +            size_hint: 1, 0.3
       +        GridLayout:
       +            rows: 1
       +            spacing: '12dp'
       +            size_hint: 1, None
       +            height: self.minimum_height
       +            WizardButton:
       +                id: back
       +                text: _('Back')
       +                root: root
       +            WizardButton:
       +                id: next
       +                text: _('Next')
       +                root: root
       +                disabled: root.value == ''
       +
       +
       +<WizardMultisigDialog>
       +    value: 'next'
       +    Widget
       +        size_hint: 1, 1
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: _("Choose the number of signatures needed to unlock funds in your wallet")
       +    Widget
       +        size_hint: 1, 1
       +    GridLayout:
       +        orientation: 'vertical'
       +        cols: 2
       +        spacing: '14dp'
       +        size_hint: 1, 1
       +        height: self.minimum_height
       +        Label:
       +            color: root.text_color
       +            text: _('From {} cosigners').format(n.value)
       +        Slider:
       +            id: n
       +            range: 2, 5
       +            step: 1
       +            value: 2
       +        Label:
       +            color: root.text_color
       +            text: _('Require {} signatures').format(m.value)
       +        Slider:
       +            id: m
       +            range: 1, n.value
       +            step: 1
       +            value: 2
       +
       +
       +<WizardChoiceDialog>
       +    message : ''
       +    Widget:
       +        size_hint: 1, 1
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: root.message
       +    Widget
       +        size_hint: 1, 1
       +    GridLayout:
       +        row_default_height: '48dp'
       +        orientation: 'vertical'
       +        id: choices
       +        cols: 1
       +        spacing: '14dp'
       +        size_hint: 1, None
       +
       +<WizardConfirmDialog>
       +    message : ''
       +    Widget:
       +        size_hint: 1, 1
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: root.message
       +    Widget
       +        size_hint: 1, 1
       +
       +<WizardTOSDialog>
       +    message : ''
       +    size_hint: 1, 1
       +    ScrollView:
       +        size_hint: 1, 1
       +        TextInput:
       +            color: root.text_color
       +            size_hint: 1, None
       +            text_size: self.width, None
       +            height: self.minimum_height
       +            text: root.message
       +            disabled: True
       +
       +<WizardEmailDialog>
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: 'Please enter your email address'
       +    WizardTextInput:
       +        id: email
       +        on_text: Clock.schedule_once(root.on_text)
       +        multiline: False
       +        on_text_validate: Clock.schedule_once(root.on_enter)
       +
       +<WizardKnownOTPDialog>
       +    message : ''
       +    message2: ''
       +    Widget:
       +        size_hint: 1, 1
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: root.message
       +    Widget
       +        size_hint: 1, 1
       +    WizardTextInput:
       +        id: otp
       +        on_text: Clock.schedule_once(root.on_text)
       +        multiline: False
       +        on_text_validate: Clock.schedule_once(root.on_enter)
       +    Widget
       +        size_hint: 1, 1
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: root.message2
       +    Widget
       +        size_hint: 1, 1
       +        height: '48sp'
       +    BoxLayout:
       +        orientation: 'horizontal'
       +        WizardButton:
       +            id: cb
       +            text: _('Request new secret')
       +            on_release: root.request_new_secret()
       +            size_hint: 1, None
       +        WizardButton:
       +            id: abort
       +            text: _('Abort creation')
       +            on_release: root.abort_wallet_creation()
       +            size_hint: 1, None
       +
       +
       +<WizardNewOTPDialog>
       +    message : ''
       +    message2 : ''
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: root.message
       +    QRCodeWidget:
       +        id: qr
       +        size_hint: 1, 1
       +    Label:
       +        color: root.text_color
       +        size_hint: 1, None
       +        text_size: self.width, None
       +        height: self.texture_size[1]
       +        text: root.message2
       +    WizardTextInput:
       +        id: otp
       +        on_text: Clock.schedule_once(root.on_text)
       +        multiline: False
       +        on_text_validate: Clock.schedule_once(root.on_enter)
       +
       +<MButton@Button>:
       +    size_hint: 1, None
       +    height: '33dp'
       +    on_release:
       +        self.parent.update_amount(self.text)
       +
       +<WordButton@Button>:
       +    size_hint: None, None
       +    padding: '5dp', '5dp'
       +    text_size: None, self.height
       +    width: self.texture_size[0]
       +    height: '30dp'
       +    on_release:
       +        self.parent.new_word(self.text)
       +
       +
       +<SeedButton@Button>:
       +    height: dp(100)
       +    border: 4, 4, 4, 4
       +    halign: 'justify'
       +    valign: 'top'
       +    font_size: '18dp'
       +    text_size: self.width - dp(24), self.height - dp(12)
       +    color: .1, .1, .1, 1
       +    background_normal: 'atlas://electrum/gui/kivy/theming/light/white_bg_round_top'
       +    background_down: self.background_normal
       +    size_hint_y: None
       +
       +
       +<SeedLabel@Label>:
       +    font_size: '12sp'
       +    text_size: self.width, None
       +    size_hint: 1, None
       +    height: self.texture_size[1]
       +    halign: 'justify'
       +    valign: 'middle'
       +    border: 4, 4, 4, 4
       +
       +
       +<RestoreSeedDialog>
       +    message: ''
       +    word: ''
       +    BigLabel:
       +        text: "ENTER YOUR SEED PHRASE"
       +    GridLayout
       +        cols: 1
       +        padding: 0, '12dp'
       +        orientation: 'vertical'
       +        spacing: '12dp'
       +        size_hint: 1, None
       +        height: self.minimum_height
       +        SeedButton:
       +            id: text_input_seed
       +            text: ''
       +            on_text: Clock.schedule_once(root.on_text)
       +            on_release: root.options_dialog()
       +        SeedLabel:
       +            text: root.message
       +        BoxLayout:
       +            id: suggestions
       +            height: '35dp'
       +            size_hint: 1, None
       +            new_word: root.on_word
       +        BoxLayout:
       +            id: line1
       +            update_amount: root.update_text
       +            size_hint: 1, None
       +            height: '30dp'
       +            MButton:
       +                text: 'Q'
       +            MButton:
       +                text: 'W'
       +            MButton:
       +                text: 'E'
       +            MButton:
       +                text: 'R'
       +            MButton:
       +                text: 'T'
       +            MButton:
       +                text: 'Y'
       +            MButton:
       +                text: 'U'
       +            MButton:
       +                text: 'I'
       +            MButton:
       +                text: 'O'
       +            MButton:
       +                text: 'P'
       +        BoxLayout:
       +            id: line2
       +            update_amount: root.update_text
       +            size_hint: 1, None
       +            height: '30dp'
       +            Widget:
       +                size_hint: 0.5, None
       +                height: '33dp'
       +            MButton:
       +                text: 'A'
       +            MButton:
       +                text: 'S'
       +            MButton:
       +                text: 'D'
       +            MButton:
       +                text: 'F'
       +            MButton:
       +                text: 'G'
       +            MButton:
       +                text: 'H'
       +            MButton:
       +                text: 'J'
       +            MButton:
       +                text: 'K'
       +            MButton:
       +                text: 'L'
       +            Widget:
       +                size_hint: 0.5, None
       +                height: '33dp'
       +        BoxLayout:
       +            id: line3
       +            update_amount: root.update_text
       +            size_hint: 1, None
       +            height: '30dp'
       +            Widget:
       +                size_hint: 1, None
       +            MButton:
       +                text: 'Z'
       +            MButton:
       +                text: 'X'
       +            MButton:
       +                text: 'C'
       +            MButton:
       +                text: 'V'
       +            MButton:
       +                text: 'B'
       +            MButton:
       +                text: 'N'
       +            MButton:
       +                text: 'M'
       +            MButton:
       +                text: ' '
       +            MButton:
       +                text: '<'
       +
       +<AddXpubDialog>
       +    title: ''
       +    message: ''
       +    BigLabel:
       +        text: root.title
       +    GridLayout
       +        cols: 1
       +        padding: 0, '12dp'
       +        orientation: 'vertical'
       +        spacing: '12dp'
       +        size_hint: 1, None
       +        height: self.minimum_height
       +        SeedButton:
       +            id: text_input
       +            text: ''
       +            on_text: Clock.schedule_once(root.check_text)
       +        SeedLabel:
       +            text: root.message
       +    GridLayout
       +        rows: 1
       +        spacing: '12dp'
       +        size_hint: 1, None
       +        height: self.minimum_height
       +        IconButton:
       +            id: scan
       +            height: '48sp'
       +            on_release: root.scan_xpub()
       +            icon: 'atlas://electrum/gui/kivy/theming/light/camera'
       +            size_hint: 1, None
       +        WizardButton:
       +            text: _('Paste')
       +            on_release: root.do_paste()
       +        WizardButton:
       +            text: _('Clear')
       +            on_release: root.do_clear()
       +
       +
       +<ShowXpubDialog>
       +    xpub: ''
       +    message: _('Here is your master public key. Share it with your cosigners.')
       +    BigLabel:
       +        text: "MASTER PUBLIC KEY"
       +    GridLayout
       +        cols: 1
       +        padding: 0, '12dp'
       +        orientation: 'vertical'
       +        spacing: '12dp'
       +        size_hint: 1, None
       +        height: self.minimum_height
       +        SeedButton:
       +            id: text_input
       +            text: root.xpub
       +        SeedLabel:
       +            text: root.message
       +    GridLayout
       +        rows: 1
       +        spacing: '12dp'
       +        size_hint: 1, None
       +        height: self.minimum_height
       +        WizardButton:
       +            text: _('QR code')
       +            on_release: root.do_qr()
       +        WizardButton:
       +            text: _('Copy')
       +            on_release: root.do_copy()
       +        WizardButton:
       +            text: _('Share')
       +            on_release: root.do_share()
       +
       +
       +<ShowSeedDialog>
       +    spacing: '12dp'
       +    value: 'next'
       +    BigLabel:
       +        text: "PLEASE WRITE DOWN YOUR SEED PHRASE"
       +    GridLayout:
       +        id: grid
       +        cols: 1
       +        pos_hint: {'center_y': .5}
       +        size_hint_y: None
       +        height: self.minimum_height
       +        orientation: 'vertical'
       +        spacing: '12dp'
       +        SeedButton:
       +            text: root.seed_text
       +            on_release: root.options_dialog()
       +        SeedLabel:
       +            text: root.message
       +
       +
       +<LineDialog>
       +
       +    BigLabel:
       +        text: root.title
       +    SeedLabel:
       +        text: root.message
       +    TextInput:
       +        id: passphrase_input
       +        multiline: False
       +        size_hint: 1, None
       +        height: '27dp'
       +    SeedLabel:
       +        text: root.warning
       +
       +''')
       +
       +
       +
       +class WizardDialog(EventsDialog):
       +    ''' Abstract dialog to be used as the base for all Create Account Dialogs
       +    '''
       +    crcontent = ObjectProperty(None)
       +
       +    def __init__(self, wizard, **kwargs):
       +        super(WizardDialog, self).__init__()
       +        self.wizard = wizard
       +        self.ids.back.disabled = not wizard.can_go_back()
       +        self.app = App.get_running_app()
       +        self.run_next = kwargs['run_next']
       +        _trigger_size_dialog = Clock.create_trigger(self._size_dialog)
       +        Window.bind(size=_trigger_size_dialog,
       +                    rotation=_trigger_size_dialog)
       +        _trigger_size_dialog()
       +        self._on_release = False
       +
       +    def _size_dialog(self, dt):
       +        app = App.get_running_app()
       +        if app.ui_mode[0] == 'p':
       +            self.size = Window.size
       +        else:
       +            #tablet
       +            if app.orientation[0] == 'p':
       +                #portrait
       +                self.size = Window.size[0]/1.67, Window.size[1]/1.4
       +            else:
       +                self.size = Window.size[0]/2.5, Window.size[1]
       +
       +    def add_widget(self, widget, index=0):
       +        if not self.crcontent:
       +            super(WizardDialog, self).add_widget(widget)
       +        else:
       +            self.crcontent.add_widget(widget, index=index)
       +
       +    def on_dismiss(self):
       +        app = App.get_running_app()
       +        if app.wallet is None and not self._on_release:
       +            app.stop()
       +
       +    def get_params(self, button):
       +        return (None,)
       +
       +    def on_release(self, button):
       +        self._on_release = True
       +        self.close()
       +        if not button:
       +            self.parent.dispatch('on_wizard_complete', None)
       +            return
       +        if button is self.ids.back:
       +            self.wizard.go_back()
       +            return
       +        params = self.get_params(button)
       +        self.run_next(*params)
       +
       +
       +class WizardMultisigDialog(WizardDialog):
       +
       +    def get_params(self, button):
       +        m = self.ids.m.value
       +        n = self.ids.n.value
       +        return m, n
       +
       +
       +class WizardOTPDialogBase(WizardDialog):
       +
       +    def get_otp(self):
       +        otp = self.ids.otp.text
       +        if len(otp) != 6:
       +            return
       +        try:
       +            return int(otp)
       +        except:
       +            return
       +
       +    def on_text(self, dt):
       +        self.ids.next.disabled = self.get_otp() is None
       +
       +    def on_enter(self, dt):
       +        # press next
       +        next = self.ids.next
       +        if not next.disabled:
       +            next.dispatch('on_release')
       +
       +
       +class WizardKnownOTPDialog(WizardOTPDialogBase):
       +
       +    def __init__(self, wizard, **kwargs):
       +        WizardOTPDialogBase.__init__(self, wizard, **kwargs)
       +        self.message = _("This wallet is already registered with TrustedCoin. To finalize wallet creation, please enter your Google Authenticator Code.")
       +        self.message2 =_("If you have lost your Google Authenticator account, you can request a new secret. You will need to retype your seed.")
       +        self.request_new = False
       +
       +    def get_params(self, button):
       +        return (self.get_otp(), self.request_new)
       +
       +    def request_new_secret(self):
       +        self.request_new = True
       +        self.on_release(True)
       +
       +    def abort_wallet_creation(self):
       +        self._on_release = True
       +        os.unlink(self.wizard.storage.path)
       +        self.wizard.terminate()
       +        self.dismiss()
       +
       +
       +class WizardNewOTPDialog(WizardOTPDialogBase):
       +
       +    def __init__(self, wizard, **kwargs):
       +        WizardOTPDialogBase.__init__(self, wizard, **kwargs)
       +        otp_secret = kwargs['otp_secret']
       +        uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
       +        self.message = "Please scan the following QR code in Google Authenticator. You may also use the secret key: %s"%otp_secret
       +        self.message2 = _('Then, enter your Google Authenticator code:')
       +        self.ids.qr.set_data(uri)
       +
       +    def get_params(self, button):
       +        return (self.get_otp(), False)
       +
       +class WizardTOSDialog(WizardDialog):
       +
       +    def __init__(self, wizard, **kwargs):
       +        WizardDialog.__init__(self, wizard, **kwargs)
       +        self.ids.next.text = 'Accept'
       +        self.ids.next.disabled = False
       +        self.message = kwargs['tos']
       +        self.message2 = _('Enter your email address:')
       +
       +class WizardEmailDialog(WizardDialog):
       +
       +    def get_params(self, button):
       +        return (self.ids.email.text,)
       +
       +    def on_text(self, dt):
       +        self.ids.next.disabled = not is_valid_email(self.ids.email.text)
       +
       +    def on_enter(self, dt):
       +        # press next
       +        next = self.ids.next
       +        if not next.disabled:
       +            next.dispatch('on_release')
       +
       +class WizardConfirmDialog(WizardDialog):
       +
       +    def __init__(self, wizard, **kwargs):
       +        super(WizardConfirmDialog, self).__init__(wizard, **kwargs)
       +        self.message = kwargs.get('message', '')
       +        self.value = 'ok'
       +
       +    def on_parent(self, instance, value):
       +        if value:
       +            app = App.get_running_app()
       +            self._back = _back = partial(app.dispatch, 'on_back')
       +
       +    def get_params(self, button):
       +        return (True,)
       +
       +class WizardChoiceDialog(WizardDialog):
       +
       +    def __init__(self, wizard, **kwargs):
       +        super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
       +        self.message = kwargs.get('message', '')
       +        choices = kwargs.get('choices', [])
       +        layout = self.ids.choices
       +        layout.bind(minimum_height=layout.setter('height'))
       +        for action, text in choices:
       +            l = WizardButton(text=text)
       +            l.action = action
       +            l.height = '48dp'
       +            l.root = self
       +            layout.add_widget(l)
       +
       +    def on_parent(self, instance, value):
       +        if value:
       +            app = App.get_running_app()
       +            self._back = _back = partial(app.dispatch, 'on_back')
       +
       +    def get_params(self, button):
       +        return (button.action,)
       +
       +
       +
       +class LineDialog(WizardDialog):
       +    title = StringProperty('')
       +    message = StringProperty('')
       +    warning = StringProperty('')
       +
       +    def __init__(self, wizard, **kwargs):
       +        WizardDialog.__init__(self, wizard, **kwargs)
       +        self.ids.next.disabled = False
       +
       +    def get_params(self, b):
       +        return (self.ids.passphrase_input.text,)
       +
       +class ShowSeedDialog(WizardDialog):
       +    seed_text = StringProperty('')
       +    message = _("If you forget your PIN or lose your device, your seed phrase will be the only way to recover your funds.")
       +    ext = False
       +
       +    def __init__(self, wizard, **kwargs):
       +        super(ShowSeedDialog, self).__init__(wizard, **kwargs)
       +        self.seed_text = kwargs['seed_text']
       +
       +    def on_parent(self, instance, value):
       +        if value:
       +            app = App.get_running_app()
       +            self._back = _back = partial(self.ids.back.dispatch, 'on_release')
       +
       +    def options_dialog(self):
       +        from .seed_options import SeedOptionsDialog
       +        def callback(status):
       +            self.ext = status
       +        d = SeedOptionsDialog(self.ext, callback)
       +        d.open()
       +
       +    def get_params(self, b):
       +        return (self.ext,)
       +
       +
       +class WordButton(Button):
       +    pass
       +
       +class WizardButton(Button):
       +    pass
       +
       +
       +class RestoreSeedDialog(WizardDialog):
       +
       +    def __init__(self, wizard, **kwargs):
       +        super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
       +        self._test = kwargs['test']
       +        from electrum.mnemonic import Mnemonic
       +        from electrum.old_mnemonic import words as old_wordlist
       +        self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
       +        self.ids.text_input_seed.text = test_seed if is_test else ''
       +        self.message = _('Please type your seed phrase using the virtual keyboard.')
       +        self.title = _('Enter Seed')
       +        self.ext = False
       +
       +    def options_dialog(self):
       +        from .seed_options import SeedOptionsDialog
       +        def callback(status):
       +            self.ext = status
       +        d = SeedOptionsDialog(self.ext, callback)
       +        d.open()
       +
       +    def get_suggestions(self, prefix):
       +        for w in self.words:
       +            if w.startswith(prefix):
       +                yield w
       +
       +    def on_text(self, dt):
       +        self.ids.next.disabled = not bool(self._test(self.get_text()))
       +
       +        text = self.ids.text_input_seed.text
       +        if not text:
       +            last_word = ''
       +        elif text[-1] == ' ':
       +            last_word = ''
       +        else:
       +            last_word = text.split(' ')[-1]
       +
       +        enable_space = False
       +        self.ids.suggestions.clear_widgets()
       +        suggestions = [x for x in self.get_suggestions(last_word)]
       +
       +        if last_word in suggestions:
       +            b = WordButton(text=last_word)
       +            self.ids.suggestions.add_widget(b)
       +            enable_space = True
       +
       +        for w in suggestions:
       +            if w != last_word and len(suggestions) < 10:
       +                b = WordButton(text=w)
       +                self.ids.suggestions.add_widget(b)
       +
       +        i = len(last_word)
       +        p = set()
       +        for x in suggestions:
       +            if len(x)>i: p.add(x[i])
       +
       +        for line in [self.ids.line1, self.ids.line2, self.ids.line3]:
       +            for c in line.children:
       +                if isinstance(c, Button):
       +                    if c.text in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
       +                        c.disabled = (c.text.lower() not in p) and bool(last_word)
       +                    elif c.text == ' ':
       +                        c.disabled = not enable_space
       +
       +    def on_word(self, w):
       +        text = self.get_text()
       +        words = text.split(' ')
       +        words[-1] = w
       +        text = ' '.join(words)
       +        self.ids.text_input_seed.text = text + ' '
       +        self.ids.suggestions.clear_widgets()
       +
       +    def get_text(self):
       +        ti = self.ids.text_input_seed
       +        return ' '.join(ti.text.strip().split())
       +
       +    def update_text(self, c):
       +        c = c.lower()
       +        text = self.ids.text_input_seed.text
       +        if c == '<':
       +            text = text[:-1]
       +        else:
       +            text += c
       +        self.ids.text_input_seed.text = text
       +
       +    def on_parent(self, instance, value):
       +        if value:
       +            tis = self.ids.text_input_seed
       +            tis.focus = True
       +            #tis._keyboard.bind(on_key_down=self.on_key_down)
       +            self._back = _back = partial(self.ids.back.dispatch,
       +                                         'on_release')
       +            app = App.get_running_app()
       +
       +    def on_key_down(self, keyboard, keycode, key, modifiers):
       +        if keycode[0] in (13, 271):
       +            self.on_enter()
       +            return True
       +
       +    def on_enter(self):
       +        #self._remove_keyboard()
       +        # press next
       +        next = self.ids.next
       +        if not next.disabled:
       +            next.dispatch('on_release')
       +
       +    def _remove_keyboard(self):
       +        tis = self.ids.text_input_seed
       +        if tis._keyboard:
       +            tis._keyboard.unbind(on_key_down=self.on_key_down)
       +            tis.focus = False
       +
       +    def get_params(self, b):
       +        return (self.get_text(), False, self.ext)
       +
       +
       +class ConfirmSeedDialog(RestoreSeedDialog):
       +    def get_params(self, b):
       +        return (self.get_text(),)
       +    def options_dialog(self):
       +        pass
       +
       +
       +class ShowXpubDialog(WizardDialog):
       +
       +    def __init__(self, wizard, **kwargs):
       +        WizardDialog.__init__(self, wizard, **kwargs)
       +        self.xpub = kwargs['xpub']
       +        self.ids.next.disabled = False
       +
       +    def do_copy(self):
       +        self.app._clipboard.copy(self.xpub)
       +
       +    def do_share(self):
       +        self.app.do_share(self.xpub, _("Master Public Key"))
       +
       +    def do_qr(self):
       +        from .qr_dialog import QRDialog
       +        popup = QRDialog(_("Master Public Key"), self.xpub, True)
       +        popup.open()
       +
       +
       +class AddXpubDialog(WizardDialog):
       +
       +    def __init__(self, wizard, **kwargs):
       +        WizardDialog.__init__(self, wizard, **kwargs)
       +        self.is_valid = kwargs['is_valid']
       +        self.title = kwargs['title']
       +        self.message = kwargs['message']
       +        self.allow_multi = kwargs.get('allow_multi', False)
       +
       +    def check_text(self, dt):
       +        self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
       +
       +    def get_text(self):
       +        ti = self.ids.text_input
       +        return ti.text.strip()
       +
       +    def get_params(self, button):
       +        return (self.get_text(),)
       +
       +    def scan_xpub(self):
       +        def on_complete(text):
       +            if self.allow_multi:
       +                self.ids.text_input.text += text + '\n'
       +            else:
       +                self.ids.text_input.text = text
       +        self.app.scan_qr(on_complete)
       +
       +    def do_paste(self):
       +        self.ids.text_input.text = test_xpub if is_test else self.app._clipboard.paste()
       +
       +    def do_clear(self):
       +        self.ids.text_input.text = ''
       +
       +
       +
       +
       +class InstallWizard(BaseWizard, Widget):
       +    '''
       +    events::
       +        `on_wizard_complete` Fired when the wizard is done creating/ restoring
       +        wallet/s.
       +    '''
       +
       +    __events__ = ('on_wizard_complete', )
       +
       +    def on_wizard_complete(self, wallet):
       +        """overriden by main_window"""
       +        pass
       +
       +    def waiting_dialog(self, task, msg, on_finished=None):
       +        '''Perform a blocking task in the background by running the passed
       +        method in a thread.
       +        '''
       +        def target():
       +            # run your threaded function
       +            try:
       +                task()
       +            except Exception as err:
       +                self.show_error(str(err))
       +            # on  completion hide message
       +            Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
       +            if on_finished:
       +                Clock.schedule_once(lambda dt: on_finished(), -1)
       +
       +        app = App.get_running_app()
       +        app.show_info_bubble(
       +            text=msg, icon='atlas://electrum/gui/kivy/theming/light/important',
       +            pos=Window.center, width='200sp', arrow_pos=None, modal=True)
       +        t = threading.Thread(target = target)
       +        t.start()
       +
       +    def terminate(self, **kwargs):
       +        self.dispatch('on_wizard_complete', self.wallet)
       +
       +    def choice_dialog(self, **kwargs):
       +        choices = kwargs['choices']
       +        if len(choices) > 1:
       +            WizardChoiceDialog(self, **kwargs).open()
       +        else:
       +            f = kwargs['run_next']
       +            f(choices[0][0])
       +
       +    def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
       +    def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
       +    def line_dialog(self, **kwargs): LineDialog(self, **kwargs).open()
       +
       +    def confirm_seed_dialog(self, **kwargs):
       +        kwargs['title'] = _('Confirm Seed')
       +        kwargs['message'] = _('Please retype your seed phrase, to confirm that you properly saved it')
       +        ConfirmSeedDialog(self, **kwargs).open()
       +
       +    def restore_seed_dialog(self, **kwargs):
       +        RestoreSeedDialog(self, **kwargs).open()
       +
       +    def confirm_dialog(self, **kwargs):
       +        WizardConfirmDialog(self, **kwargs).open()
       +
       +    def tos_dialog(self, **kwargs):
       +        WizardTOSDialog(self, **kwargs).open()
       +
       +    def email_dialog(self, **kwargs):
       +        WizardEmailDialog(self, **kwargs).open()
       +
       +    def otp_dialog(self, **kwargs):
       +        if kwargs['otp_secret']:
       +            WizardNewOTPDialog(self, **kwargs).open()
       +        else:
       +            WizardKnownOTPDialog(self, **kwargs).open()
       +
       +    def add_xpub_dialog(self, **kwargs):
       +        kwargs['message'] += ' ' + _('Use the camera button to scan a QR code.')
       +        AddXpubDialog(self, **kwargs).open()
       +
       +    def add_cosigner_dialog(self, **kwargs):
       +        kwargs['title'] = _("Add Cosigner") + " %d"%kwargs['index']
       +        kwargs['message'] = _('Please paste your cosigners master public key, or scan it using the camera button.')
       +        AddXpubDialog(self, **kwargs).open()
       +
       +    def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()
       +
       +    def show_message(self, msg): self.show_error(msg)
       +
       +    def show_error(self, msg):
       +        app = App.get_running_app()
       +        Clock.schedule_once(lambda dt: app.show_error(msg))
       +
       +    def request_password(self, run_next, force_disable_encrypt_cb=False):
       +        def on_success(old_pin, pin):
       +            assert old_pin is None
       +            run_next(pin, False)
       +        def on_failure():
       +            self.show_error(_('PIN mismatch'))
       +            self.run('request_password', run_next)
       +        popup = PasswordDialog()
       +        app = App.get_running_app()
       +        popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2)
       +        popup.open()
       +
       +    def action_dialog(self, action, run_next):
       +        f = getattr(self, action)
       +        f()
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/invoices.py b/electrum/gui/kivy/uix/dialogs/invoices.py
       t@@ -0,0 +1,169 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +from decimal import Decimal
       +
       +Builder.load_string('''
       +<InvoicesLabel@Label>
       +    #color: .305, .309, .309, 1
       +    text_size: self.width, None
       +    halign: 'left'
       +    valign: 'top'
       +
       +<InvoiceItem@CardItem>
       +    requestor: ''
       +    memo: ''
       +    amount: ''
       +    status: ''
       +    date: ''
       +    icon: 'atlas://electrum/gui/kivy/theming/light/important'
       +    Image:
       +        id: icon
       +        source: root.icon
       +        size_hint: None, 1
       +        width: self.height *.54
       +        mipmap: True
       +    BoxLayout:
       +        spacing: '8dp'
       +        height: '32dp'
       +        orientation: 'vertical'
       +        Widget
       +        InvoicesLabel:
       +            text: root.requestor
       +            shorten: True
       +        Widget
       +        InvoicesLabel:
       +            text: root.memo
       +            color: .699, .699, .699, 1
       +            font_size: '13sp'
       +            shorten: True
       +        Widget
       +    BoxLayout:
       +        spacing: '8dp'
       +        height: '32dp'
       +        orientation: 'vertical'
       +        Widget
       +        InvoicesLabel:
       +            text: root.amount
       +            font_size: '15sp'
       +            halign: 'right'
       +            width: '110sp'
       +        Widget
       +        InvoicesLabel:
       +            text: root.status
       +            font_size: '13sp'
       +            halign: 'right'
       +            color: .699, .699, .699, 1
       +        Widget
       +
       +
       +<InvoicesDialog@Popup>
       +    id: popup
       +    title: _('Invoices')
       +    BoxLayout:
       +        id: box
       +        orientation: 'vertical'
       +        spacing: '1dp'
       +        ScrollView:
       +            GridLayout:
       +                cols: 1
       +                id: invoices_container
       +                size_hint: 1, None
       +                height: self.minimum_height
       +                spacing: '2dp'
       +                padding: '12dp'
       +''')
       +
       +from kivy.properties import BooleanProperty
       +from electrum.gui.kivy.i18n import _
       +from electrum.util import format_time
       +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
       +from electrum.gui.kivy.uix.context_menu import ContextMenu
       +
       +invoice_text = {
       +    PR_UNPAID:_('Pending'),
       +    PR_UNKNOWN:_('Unknown'),
       +    PR_PAID:_('Paid'),
       +    PR_EXPIRED:_('Expired')
       +}
       +pr_icon = {
       +    PR_UNPAID: 'atlas://electrum/gui/kivy/theming/light/important',
       +    PR_UNKNOWN: 'atlas://electrum/gui/kivy/theming/light/important',
       +    PR_PAID: 'atlas://electrum/gui/kivy/theming/light/confirmed',
       +    PR_EXPIRED: 'atlas://electrum/gui/kivy/theming/light/close'
       +}
       +
       +
       +class InvoicesDialog(Factory.Popup):
       +
       +    def __init__(self, app, screen, callback):
       +        Factory.Popup.__init__(self)
       +        self.app = app
       +        self.screen = screen
       +        self.callback = callback
       +        self.cards = {}
       +        self.context_menu = None
       +
       +    def get_card(self, pr):
       +        key = pr.get_id()
       +        ci = self.cards.get(key)
       +        if ci is None:
       +            ci = Factory.InvoiceItem()
       +            ci.key = key
       +            ci.screen = self
       +            self.cards[key] = ci
       +        ci.requestor = pr.get_requestor()
       +        ci.memo = pr.get_memo()
       +        amount = pr.get_amount()
       +        if amount:
       +            ci.amount = self.app.format_amount_and_units(amount)
       +            status = self.app.wallet.invoices.get_status(ci.key)
       +            ci.status = invoice_text[status]
       +            ci.icon = pr_icon[status]
       +        else:
       +            ci.amount = _('No Amount')
       +            ci.status = ''
       +        exp = pr.get_expiration_date()
       +        ci.date = format_time(exp) if exp else _('Never')
       +        return ci
       +
       +    def update(self):
       +        self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)]
       +        invoices_list = self.ids.invoices_container
       +        invoices_list.clear_widgets()
       +        _list = self.app.wallet.invoices.sorted_list()
       +        for pr in _list:
       +            ci = self.get_card(pr)
       +            invoices_list.add_widget(ci)
       +
       +    def do_pay(self, obj):
       +        self.hide_menu()
       +        self.dismiss()
       +        pr = self.app.wallet.invoices.get(obj.key)
       +        self.app.on_pr(pr)
       +
       +    def do_view(self, obj):
       +        pr = self.app.wallet.invoices.get(obj.key)
       +        pr.verify(self.app.wallet.contacts)
       +        self.app.show_pr_details(pr.get_dict(), obj.status, True)
       +
       +    def do_delete(self, obj):
       +        from .question import Question
       +        def cb(result):
       +            if result:
       +                self.app.wallet.invoices.remove(obj.key)
       +            self.hide_menu()
       +            self.update()
       +        d = Question(_('Delete invoice?'), cb)
       +        d.open()
       +
       +    def show_menu(self, obj):
       +        self.hide_menu()
       +        self.context_menu = ContextMenu(obj, self.menu_actions)
       +        self.ids.box.add_widget(self.context_menu)
       +
       +    def hide_menu(self):
       +        if self.context_menu is not None:
       +            self.ids.box.remove_widget(self.context_menu)
       +            self.context_menu = None
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/label_dialog.py b/electrum/gui/kivy/uix/dialogs/label_dialog.py
       t@@ -0,0 +1,55 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +
       +Builder.load_string('''
       +<LabelDialog@Popup>
       +    id: popup
       +    title: ''
       +    size_hint: 0.8, 0.3
       +    pos_hint: {'top':0.9}
       +    BoxLayout:
       +        orientation: 'vertical'
       +        Widget:
       +            size_hint: 1, 0.2
       +        TextInput:
       +            id:input
       +            padding: '5dp'
       +            size_hint: 1, None
       +            height: '27dp'
       +            pos_hint: {'center_y':.5}
       +            text:''
       +            multiline: False
       +            background_normal: 'atlas://electrum/gui/kivy/theming/light/tab_btn'
       +            background_active: 'atlas://electrum/gui/kivy/theming/light/textinput_active'
       +            hint_text_color: self.foreground_color
       +            foreground_color: 1, 1, 1, 1
       +            font_size: '16dp'
       +            focus: True
       +        Widget:
       +            size_hint: 1, 0.2
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.5
       +            Button:
       +                text: 'Cancel'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release: popup.dismiss()
       +            Button:
       +                text: 'OK'
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release:
       +                    root.callback(input.text)
       +                    popup.dismiss()
       +''')
       +
       +class LabelDialog(Factory.Popup):
       +
       +    def __init__(self, title, text, callback):
       +        Factory.Popup.__init__(self)
       +        self.ids.input.text = text
       +        self.callback = callback
       +        self.title = title
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/nfc_transaction.py b/electrum/gui/kivy/uix/dialogs/nfc_transaction.py
       t@@ -0,0 +1,32 @@
       +class NFCTransactionDialog(AnimatedPopup):
       +
       +    mode = OptionProperty('send', options=('send','receive'))
       +
       +    scanner = ObjectProperty(None)
       +
       +    def __init__(self, **kwargs):
       +        # Delayed Init
       +        global NFCSCanner
       +        if NFCSCanner is None:
       +            from electrum.gui.kivy.nfc_scanner import NFCScanner
       +        self.scanner = NFCSCanner
       +
       +        super(NFCTransactionDialog, self).__init__(**kwargs)
       +        self.scanner.nfc_init()
       +        self.scanner.bind()
       +
       +    def on_parent(self, instance, value):
       +        sctr = self.ids.sctr
       +        if value:
       +            def _cmp(*l):
       +                anim = Animation(rotation=2, scale=1, opacity=1)
       +                anim.start(sctr)
       +                anim.bind(on_complete=_start)
       +
       +            def _start(*l):
       +                anim = Animation(rotation=350, scale=2, opacity=0)
       +                anim.start(sctr)
       +                anim.bind(on_complete=_cmp)
       +            _start()
       +            return
       +        Animation.cancel_all(sctr)
       +\ No newline at end of file
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py
       t@@ -0,0 +1,142 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +from decimal import Decimal
       +from kivy.clock import Clock
       +
       +from electrum.util import InvalidPassword
       +from electrum.gui.kivy.i18n import _
       +
       +Builder.load_string('''
       +
       +<PasswordDialog@Popup>
       +    id: popup
       +    title: 'Electrum'
       +    message: ''
       +    BoxLayout:
       +        size_hint: 1, 1
       +        orientation: 'vertical'
       +        Widget:
       +            size_hint: 1, 0.05
       +        Label:
       +            font_size: '20dp'
       +            text: root.message
       +            text_size: self.width, None
       +            size: self.texture_size
       +        Widget:
       +            size_hint: 1, 0.05
       +        Label:
       +            id: a
       +            font_size: '50dp'
       +            text: '*'*len(kb.password) + '-'*(6-len(kb.password))
       +            size: self.texture_size
       +        Widget:
       +            size_hint: 1, 0.05
       +        GridLayout:
       +            id: kb
       +            size_hint: 1, None
       +            height: self.minimum_height
       +            update_amount: popup.update_password
       +            password: ''
       +            on_password: popup.on_password(self.password)
       +            spacing: '2dp'
       +            cols: 3
       +            KButton:
       +                text: '1'
       +            KButton:
       +                text: '2'
       +            KButton:
       +                text: '3'
       +            KButton:
       +                text: '4'
       +            KButton:
       +                text: '5'
       +            KButton:
       +                text: '6'
       +            KButton:
       +                text: '7'
       +            KButton:
       +                text: '8'
       +            KButton:
       +                text: '9'
       +            KButton:
       +                text: 'Clear'
       +            KButton:
       +                text: '0'
       +            KButton:
       +                text: '<'
       +''')
       +
       +
       +class PasswordDialog(Factory.Popup):
       +
       +    def init(self, app, wallet, message, on_success, on_failure, is_change=0):
       +        self.app = app
       +        self.wallet = wallet
       +        self.message = message
       +        self.on_success = on_success
       +        self.on_failure = on_failure
       +        self.ids.kb.password = ''
       +        self.success = False
       +        self.is_change = is_change
       +        self.pw = None
       +        self.new_password = None
       +        self.title = 'Electrum' + ('  -  ' + self.wallet.basename() if self.wallet else '')
       +
       +    def check_password(self, password):
       +        if self.is_change > 1:
       +            return True
       +        try:
       +            self.wallet.check_password(password)
       +            return True
       +        except InvalidPassword as e:
       +            return False
       +
       +    def on_dismiss(self):
       +        if not self.success:
       +            if self.on_failure:
       +                self.on_failure()
       +            else:
       +                # keep dialog open
       +                return True
       +        else:
       +            if self.on_success:
       +                args = (self.pw, self.new_password) if self.is_change else (self.pw,)
       +                Clock.schedule_once(lambda dt: self.on_success(*args), 0.1)
       +
       +    def update_password(self, c):
       +        kb = self.ids.kb
       +        text = kb.password
       +        if c == '<':
       +            text = text[:-1]
       +        elif c == 'Clear':
       +            text = ''
       +        else:
       +            text += c
       +        kb.password = text
       +
       +    def on_password(self, pw):
       +        if len(pw) == 6:
       +            if self.check_password(pw):
       +                if self.is_change == 0:
       +                    self.success = True
       +                    self.pw = pw
       +                    self.message = _('Please wait...')
       +                    self.dismiss()
       +                elif self.is_change == 1:
       +                    self.pw = pw
       +                    self.message = _('Enter new PIN')
       +                    self.ids.kb.password = ''
       +                    self.is_change = 2
       +                elif self.is_change == 2:
       +                    self.new_password = pw
       +                    self.message = _('Confirm new PIN')
       +                    self.ids.kb.password = ''
       +                    self.is_change = 3
       +                elif self.is_change == 3:
       +                    self.success = pw == self.new_password
       +                    self.dismiss()
       +            else:
       +                self.app.show_error(_('Wrong PIN'))
       +                self.ids.kb.password = ''
   DIR diff --git a/gui/kivy/uix/dialogs/qr_dialog.py b/electrum/gui/kivy/uix/dialogs/qr_dialog.py
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/qr_scanner.py b/electrum/gui/kivy/uix/dialogs/qr_scanner.py
       t@@ -0,0 +1,44 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.lang import Builder
       +
       +Factory.register('QRScanner', module='electrum.gui.kivy.qr_scanner')
       +
       +class QrScannerDialog(Factory.AnimatedPopup):
       +
       +    __events__ = ('on_complete', )
       +
       +    def on_symbols(self, instance, value):
       +        instance.stop()
       +        self.dismiss()
       +        data = value[0].data
       +        self.dispatch('on_complete', data)
       +
       +    def on_complete(self, x):
       +        ''' Default Handler for on_complete event.
       +        '''
       +        print(x)
       +
       +
       +Builder.load_string('''
       +<QrScannerDialog>
       +    title:
       +        _(\
       +        '[size=18dp]Hold your QRCode up to the camera[/size][size=7dp]\\n[/size]')
       +    title_size: '24sp'
       +    border: 7, 7, 7, 7
       +    size_hint: None, None
       +    size: '340dp', '290dp'
       +    pos_hint: {'center_y': .53}
       +    #separator_color: .89, .89, .89, 1
       +    #separator_height: '1.2dp'
       +    #title_color: .437, .437, .437, 1
       +    #background: 'atlas://electrum/gui/kivy/theming/light/dialog'
       +    on_activate:
       +        qrscr.start()
       +        qrscr.size = self.size
       +    on_deactivate: qrscr.stop()
       +    QRScanner:
       +        id: qrscr
       +        on_symbols: root.on_symbols(*args)
       +''')
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/question.py b/electrum/gui/kivy/uix/dialogs/question.py
       t@@ -0,0 +1,53 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +from kivy.uix.checkbox import CheckBox
       +from kivy.uix.label import Label
       +from kivy.uix.widget import Widget
       +
       +from electrum.gui.kivy.i18n import _
       +
       +Builder.load_string('''
       +<Question@Popup>
       +    id: popup
       +    title: ''
       +    message: ''
       +    size_hint: 0.8, 0.5
       +    pos_hint: {'top':0.9}
       +    BoxLayout:
       +        orientation: 'vertical'
       +        Label:
       +            id: label
       +            text: root.message
       +            text_size: self.width, None
       +        Widget:
       +            size_hint: 1, 0.1
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.2
       +            Button:
       +                text: _('No')
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release:
       +                    root.callback(False)
       +                    popup.dismiss()
       +            Button:
       +                text: _('Yes')
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                on_release:
       +                    root.callback(True)
       +                    popup.dismiss()
       +''')
       +
       +
       +
       +class Question(Factory.Popup):
       +
       +    def __init__(self, msg, callback):
       +        Factory.Popup.__init__(self)
       +        self.title = _('Question')
       +        self.message = msg
       +        self.callback = callback
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/requests.py b/electrum/gui/kivy/uix/dialogs/requests.py
       t@@ -0,0 +1,157 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +from decimal import Decimal
       +
       +Builder.load_string('''
       +<RequestLabel@Label>
       +    #color: .305, .309, .309, 1
       +    text_size: self.width, None
       +    halign: 'left'
       +    valign: 'top'
       +
       +<RequestItem@CardItem>
       +    address: ''
       +    memo: ''
       +    amount: ''
       +    status: ''
       +    date: ''
       +    icon: 'atlas://electrum/gui/kivy/theming/light/important'
       +    Image:
       +        id: icon
       +        source: root.icon
       +        size_hint: None, 1
       +        width: self.height *.54
       +        mipmap: True
       +    BoxLayout:
       +        spacing: '8dp'
       +        height: '32dp'
       +        orientation: 'vertical'
       +        Widget
       +        RequestLabel:
       +            text: root.address
       +            shorten: True
       +        Widget
       +        RequestLabel:
       +            text: root.memo
       +            color: .699, .699, .699, 1
       +            font_size: '13sp'
       +            shorten: True
       +        Widget
       +    BoxLayout:
       +        spacing: '8dp'
       +        height: '32dp'
       +        orientation: 'vertical'
       +        Widget
       +        RequestLabel:
       +            text: root.amount
       +            halign: 'right'
       +            font_size: '15sp'
       +        Widget
       +        RequestLabel:
       +            text: root.status
       +            halign: 'right'
       +            font_size: '13sp'
       +            color: .699, .699, .699, 1
       +        Widget
       +
       +<RequestsDialog@Popup>
       +    id: popup
       +    title: _('Requests')
       +    BoxLayout:
       +        id:box
       +        orientation: 'vertical'
       +        spacing: '1dp'
       +        ScrollView:
       +            GridLayout:
       +                cols: 1
       +                id: requests_container
       +                size_hint: 1, None
       +                height: self.minimum_height
       +                spacing: '2dp'
       +                padding: '12dp'
       +''')
       +
       +from kivy.properties import BooleanProperty
       +from electrum.gui.kivy.i18n import _
       +from electrum.util import format_time
       +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
       +from electrum.gui.kivy.uix.context_menu import ContextMenu
       +
       +pr_icon = {
       +    PR_UNPAID: 'atlas://electrum/gui/kivy/theming/light/important',
       +    PR_UNKNOWN: 'atlas://electrum/gui/kivy/theming/light/important',
       +    PR_PAID: 'atlas://electrum/gui/kivy/theming/light/confirmed',
       +    PR_EXPIRED: 'atlas://electrum/gui/kivy/theming/light/close'
       +}
       +request_text = {
       +    PR_UNPAID: _('Pending'),
       +    PR_UNKNOWN: _('Unknown'),
       +    PR_PAID: _('Received'),
       +    PR_EXPIRED: _('Expired')
       +}
       +
       +
       +class RequestsDialog(Factory.Popup):
       +
       +    def __init__(self, app, screen, callback):
       +        Factory.Popup.__init__(self)
       +        self.app = app
       +        self.screen = screen
       +        self.callback = callback
       +        self.cards = {}
       +        self.context_menu = None
       +
       +    def get_card(self, req):
       +        address = req['address']
       +        ci = self.cards.get(address)
       +        if ci is None:
       +            ci = Factory.RequestItem()
       +            ci.address = address
       +            ci.screen = self
       +            self.cards[address] = ci
       +
       +        amount = req.get('amount')
       +        ci.amount = self.app.format_amount_and_units(amount) if amount else ''
       +        ci.memo = req.get('memo', '')
       +        status, conf = self.app.wallet.get_request_status(address)
       +        ci.status = request_text[status]
       +        ci.icon = pr_icon[status]
       +        #exp = pr.get_expiration_date()
       +        #ci.date = format_time(exp) if exp else _('Never')
       +        return ci
       +
       +    def update(self):
       +        self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)]
       +        requests_list = self.ids.requests_container
       +        requests_list.clear_widgets()
       +        _list = self.app.wallet.get_sorted_requests(self.app.electrum_config)
       +        for pr in _list:
       +            ci = self.get_card(pr)
       +            requests_list.add_widget(ci)
       +
       +    def do_show(self, obj):
       +        self.hide_menu()
       +        self.dismiss()
       +        self.app.show_request(obj.address)
       +
       +    def do_delete(self, req):
       +        from .question import Question
       +        def cb(result):
       +            if result:
       +                self.app.wallet.remove_payment_request(req.address, self.app.electrum_config)
       +                self.hide_menu()
       +                self.update()
       +        d = Question(_('Delete request'), cb)
       +        d.open()
       +
       +    def show_menu(self, obj):
       +        self.hide_menu()
       +        self.context_menu = ContextMenu(obj, self.menu_actions)
       +        self.ids.box.add_widget(self.context_menu)
       +
       +    def hide_menu(self):
       +        if self.context_menu is not None:
       +            self.ids.box.remove_widget(self.context_menu)
       +            self.context_menu = None
   DIR diff --git a/gui/kivy/uix/dialogs/seed_options.py b/electrum/gui/kivy/uix/dialogs/seed_options.py
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/settings.py b/electrum/gui/kivy/uix/dialogs/settings.py
       t@@ -0,0 +1,220 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +
       +from electrum.util import base_units_list
       +from electrum.i18n import languages
       +from electrum.gui.kivy.i18n import _
       +from electrum.plugin import run_hook
       +from electrum import coinchooser
       +
       +from .choice_dialog import ChoiceDialog
       +
       +Builder.load_string('''
       +#:import partial functools.partial
       +#:import _ electrum.gui.kivy.i18n._
       +
       +<SettingsDialog@Popup>
       +    id: settings
       +    title: _('Electrum Settings')
       +    disable_pin: False
       +    use_encryption: False
       +    BoxLayout:
       +        orientation: 'vertical'
       +        ScrollView:
       +            GridLayout:
       +                id: scrollviewlayout
       +                cols:1
       +                size_hint: 1, None
       +                height: self.minimum_height
       +                padding: '10dp'
       +                SettingsItem:
       +                    lang: settings.get_language_name()
       +                    title: 'Language' + ': ' + str(self.lang)
       +                    description: _('Language')
       +                    action: partial(root.language_dialog, self)
       +                CardSeparator
       +                SettingsItem:
       +                    disabled: root.disable_pin
       +                    title: _('PIN code')
       +                    description: _("Change your PIN code.")
       +                    action: partial(root.change_password, self)
       +                CardSeparator
       +                SettingsItem:
       +                    bu: app.base_unit
       +                    title: _('Denomination') + ': ' + self.bu
       +                    description: _("Base unit for Bitcoin amounts.")
       +                    action: partial(root.unit_dialog, self)
       +                CardSeparator
       +                SettingsItem:
       +                    status: root.fx_status()
       +                    title: _('Fiat Currency') + ': ' + self.status
       +                    description: _("Display amounts in fiat currency.")
       +                    action: partial(root.fx_dialog, self)
       +                CardSeparator
       +                SettingsItem:
       +                    status: 'ON' if bool(app.plugins.get('labels')) else 'OFF'
       +                    title: _('Labels Sync') + ': ' + self.status
       +                    description: _("Save and synchronize your labels.")
       +                    action: partial(root.plugin_dialog, 'labels', self)
       +                CardSeparator
       +                SettingsItem:
       +                    status: 'ON' if app.use_rbf else 'OFF'
       +                    title: _('Replace-by-fee') + ': ' + self.status
       +                    description: _("Create replaceable transactions.")
       +                    message:
       +                        _('If you check this box, your transactions will be marked as non-final,') \
       +                        + ' ' + _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pays higher fees.') \
       +                        + ' ' + _('Note that some merchants do not accept non-final transactions until they are confirmed.')
       +                    action: partial(root.boolean_dialog, 'use_rbf', _('Replace by fee'), self.message)
       +                CardSeparator
       +                SettingsItem:
       +                    status: _('Yes') if app.use_unconfirmed else _('No')
       +                    title: _('Spend unconfirmed') + ': ' + self.status
       +                    description: _("Use unconfirmed coins in transactions.")
       +                    message: _('Spend unconfirmed coins')
       +                    action: partial(root.boolean_dialog, 'use_unconfirmed', _('Use unconfirmed'), self.message)
       +                CardSeparator
       +                SettingsItem:
       +                    status: _('Yes') if app.use_change else _('No')
       +                    title: _('Use change addresses') + ': ' + self.status
       +                    description: _("Send your change to separate addresses.")
       +                    message: _('Send excess coins to change addresses')
       +                    action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message)
       +
       +                # disabled: there is currently only one coin selection policy
       +                #CardSeparator
       +                #SettingsItem:
       +                #    status: root.coinselect_status()
       +                #    title: _('Coin selection') + ': ' + self.status
       +                #    description: "Coin selection method"
       +                #    action: partial(root.coinselect_dialog, self)
       +''')
       +
       +
       +
       +class SettingsDialog(Factory.Popup):
       +
       +    def __init__(self, app):
       +        self.app = app
       +        self.plugins = self.app.plugins
       +        self.config = self.app.electrum_config
       +        Factory.Popup.__init__(self)
       +        layout = self.ids.scrollviewlayout
       +        layout.bind(minimum_height=layout.setter('height'))
       +        # cached dialogs
       +        self._fx_dialog = None
       +        self._proxy_dialog = None
       +        self._language_dialog = None
       +        self._unit_dialog = None
       +        self._coinselect_dialog = None
       +
       +    def update(self):
       +        self.wallet = self.app.wallet
       +        self.disable_pin = self.wallet.is_watching_only() if self.wallet else True
       +        self.use_encryption = self.wallet.has_password() if self.wallet else False
       +
       +    def get_language_name(self):
       +        return languages.get(self.config.get('language', 'en_UK'), '')
       +
       +    def change_password(self, item, dt):
       +        self.app.change_password(self.update)
       +
       +    def language_dialog(self, item, dt):
       +        if self._language_dialog is None:
       +            l = self.config.get('language', 'en_UK')
       +            def cb(key):
       +                self.config.set_key("language", key, True)
       +                item.lang = self.get_language_name()
       +                self.app.language = key
       +            self._language_dialog = ChoiceDialog(_('Language'), languages, l, cb)
       +        self._language_dialog.open()
       +
       +    def unit_dialog(self, item, dt):
       +        if self._unit_dialog is None:
       +            def cb(text):
       +                self.app._set_bu(text)
       +                item.bu = self.app.base_unit
       +            self._unit_dialog = ChoiceDialog(_('Denomination'), base_units_list,
       +                                             self.app.base_unit, cb, keep_choice_order=True)
       +        self._unit_dialog.open()
       +
       +    def coinselect_status(self):
       +        return coinchooser.get_name(self.app.electrum_config)
       +
       +    def coinselect_dialog(self, item, dt):
       +        if self._coinselect_dialog is None:
       +            choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
       +            chooser_name = coinchooser.get_name(self.config)
       +            def cb(text):
       +                self.config.set_key('coin_chooser', text)
       +                item.status = text
       +            self._coinselect_dialog = ChoiceDialog(_('Coin selection'), choosers, chooser_name, cb)
       +        self._coinselect_dialog.open()
       +
       +    def proxy_status(self):
       +        server, port, protocol, proxy, auto_connect = self.app.network.get_parameters()
       +        return proxy.get('host') +':' + proxy.get('port') if proxy else _('None')
       +
       +    def proxy_dialog(self, item, dt):
       +        if self._proxy_dialog is None:
       +            server, port, protocol, proxy, auto_connect = self.app.network.get_parameters()
       +            def callback(popup):
       +                if popup.ids.mode.text != 'None':
       +                    proxy = {
       +                        'mode':popup.ids.mode.text,
       +                        'host':popup.ids.host.text,
       +                        'port':popup.ids.port.text,
       +                        'user':popup.ids.user.text,
       +                        'password':popup.ids.password.text
       +                    }
       +                else:
       +                    proxy = None
       +                self.app.network.set_parameters(server, port, protocol, proxy, auto_connect)
       +                item.status = self.proxy_status()
       +            popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/proxy.kv')
       +            popup.ids.mode.text = proxy.get('mode') if proxy else 'None'
       +            popup.ids.host.text = proxy.get('host') if proxy else ''
       +            popup.ids.port.text = proxy.get('port') if proxy else ''
       +            popup.ids.user.text = proxy.get('user') if proxy else ''
       +            popup.ids.password.text = proxy.get('password') if proxy else ''
       +            popup.on_dismiss = lambda: callback(popup)
       +            self._proxy_dialog = popup
       +        self._proxy_dialog.open()
       +
       +    def plugin_dialog(self, name, label, dt):
       +        from .checkbox_dialog import CheckBoxDialog
       +        def callback(status):
       +            self.plugins.enable(name) if status else self.plugins.disable(name)
       +            label.status = 'ON' if status else 'OFF'
       +        status = bool(self.plugins.get(name))
       +        dd = self.plugins.descriptions.get(name)
       +        descr = dd.get('description')
       +        fullname = dd.get('fullname')
       +        d = CheckBoxDialog(fullname, descr, status, callback)
       +        d.open()
       +
       +    def fee_status(self):
       +        return self.config.get_fee_status()
       +
       +    def boolean_dialog(self, name, title, message, dt):
       +        from .checkbox_dialog import CheckBoxDialog
       +        CheckBoxDialog(title, message, getattr(self.app, name), lambda x: setattr(self.app, name, x)).open()
       +
       +    def fx_status(self):
       +        fx = self.app.fx
       +        if fx.is_enabled():
       +            source = fx.exchange.name()
       +            ccy = fx.get_currency()
       +            return '%s [%s]' %(ccy, source)
       +        else:
       +            return _('None')
       +
       +    def fx_dialog(self, label, dt):
       +        if self._fx_dialog is None:
       +            from .fx_dialog import FxDialog
       +            def cb():
       +                label.status = self.fx_status()
       +            self._fx_dialog = FxDialog(self.app, self.plugins, self.config, cb)
       +        self._fx_dialog.open()
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/tx_dialog.py b/electrum/gui/kivy/uix/dialogs/tx_dialog.py
       t@@ -0,0 +1,184 @@
       +from kivy.app import App
       +from kivy.factory import Factory
       +from kivy.properties import ObjectProperty
       +from kivy.lang import Builder
       +from kivy.clock import Clock
       +from kivy.uix.label import Label
       +
       +from electrum.gui.kivy.i18n import _
       +from datetime import datetime
       +from electrum.util import InvalidPassword
       +
       +Builder.load_string('''
       +
       +<TxDialog>
       +    id: popup
       +    title: _('Transaction')
       +    is_mine: True
       +    can_sign: False
       +    can_broadcast: False
       +    can_rbf: False
       +    fee_str: ''
       +    date_str: ''
       +    date_label:''
       +    amount_str: ''
       +    tx_hash: ''
       +    status_str: ''
       +    description: ''
       +    outputs_str: ''
       +    BoxLayout:
       +        orientation: 'vertical'
       +        ScrollView:
       +            scroll_type: ['bars', 'content']
       +            bar_width: '25dp'
       +            GridLayout:
       +                height: self.minimum_height
       +                size_hint_y: None
       +                cols: 1
       +                spacing: '10dp'
       +                padding: '10dp'
       +                GridLayout:
       +                    height: self.minimum_height
       +                    size_hint_y: None
       +                    cols: 1
       +                    spacing: '10dp'
       +                    BoxLabel:
       +                        text: _('Status')
       +                        value: root.status_str
       +                    BoxLabel:
       +                        text: _('Description') if root.description else ''
       +                        value: root.description
       +                    BoxLabel:
       +                        text: root.date_label
       +                        value: root.date_str
       +                    BoxLabel:
       +                        text: _('Amount sent') if root.is_mine else _('Amount received')
       +                        value: root.amount_str
       +                    BoxLabel:
       +                        text: _('Transaction fee') if root.fee_str else ''
       +                        value: root.fee_str
       +                TopLabel:
       +                    text: _('Transaction ID') + ':' if root.tx_hash else ''
       +                TxHashLabel:
       +                    data: root.tx_hash
       +                    name: _('Transaction ID')
       +                TopLabel:
       +                    text: _('Outputs') + ':'
       +                OutputList:
       +                    id: output_list
       +        Widget:
       +            size_hint: 1, 0.1
       +
       +        BoxLayout:
       +            size_hint: 1, None
       +            height: '48dp'
       +            Button:
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                text: _('Sign') if root.can_sign else _('Broadcast') if root.can_broadcast else _('Bump fee') if root.can_rbf else ''
       +                disabled: not(root.can_sign or root.can_broadcast or root.can_rbf)
       +                opacity: 0 if self.disabled else 1
       +                on_release:
       +                    if root.can_sign: root.do_sign()
       +                    if root.can_broadcast: root.do_broadcast()
       +                    if root.can_rbf: root.do_rbf()
       +            IconButton:
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                icon: 'atlas://electrum/gui/kivy/theming/light/qrcode'
       +                on_release: root.show_qr()
       +            Button:
       +                size_hint: 0.5, None
       +                height: '48dp'
       +                text: _('Close')
       +                on_release: root.dismiss()
       +''')
       +
       +
       +class TxDialog(Factory.Popup):
       +
       +    def __init__(self, app, tx):
       +        Factory.Popup.__init__(self)
       +        self.app = app
       +        self.wallet = self.app.wallet
       +        self.tx = tx
       +
       +    def on_open(self):
       +        self.update()
       +
       +    def update(self):
       +        format_amount = self.app.format_amount_and_units
       +        tx_hash, self.status_str, self.description, self.can_broadcast, self.can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
       +        self.tx_hash = tx_hash or ''
       +        if timestamp:
       +            self.date_label = _('Date')
       +            self.date_str = datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
       +        elif exp_n:
       +            self.date_label = _('Mempool depth')
       +            self.date_str = _('{} from tip').format('%.2f MB'%(exp_n/1000000))
       +        else:
       +            self.date_label = ''
       +            self.date_str = ''
       +
       +        if amount is None:
       +            self.amount_str = _("Transaction unrelated to your wallet")
       +        elif amount > 0:
       +            self.is_mine = False
       +            self.amount_str = format_amount(amount)
       +        else:
       +            self.is_mine = True
       +            self.amount_str = format_amount(-amount)
       +        self.fee_str = format_amount(fee) if fee is not None else _('unknown')
       +        self.can_sign = self.wallet.can_sign(self.tx)
       +        self.ids.output_list.update(self.tx.outputs())
       +
       +    def do_rbf(self):
       +        from .bump_fee_dialog import BumpFeeDialog
       +        is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(self.tx)
       +        if fee is None:
       +            self.app.show_error(_("Can't bump fee: unknown fee for original transaction."))
       +            return
       +        size = self.tx.estimated_size()
       +        d = BumpFeeDialog(self.app, fee, size, self._do_rbf)
       +        d.open()
       +
       +    def _do_rbf(self, old_fee, new_fee, is_final):
       +        if new_fee is None:
       +            return
       +        delta = new_fee - old_fee
       +        if delta < 0:
       +            self.app.show_error("fee too low")
       +            return
       +        try:
       +            new_tx = self.wallet.bump_fee(self.tx, delta)
       +        except BaseException as e:
       +            self.app.show_error(str(e))
       +            return
       +        if is_final:
       +            new_tx.set_rbf(False)
       +        self.tx = new_tx
       +        self.update()
       +        self.do_sign()
       +
       +    def do_sign(self):
       +        self.app.protected(_("Enter your PIN code in order to sign this transaction"), self._do_sign, ())
       +
       +    def _do_sign(self, password):
       +        self.status_str = _('Signing') + '...'
       +        Clock.schedule_once(lambda dt: self.__do_sign(password), 0.1)
       +
       +    def __do_sign(self, password):
       +        try:
       +            self.app.wallet.sign_transaction(self.tx, password)
       +        except InvalidPassword:
       +            self.app.show_error(_("Invalid PIN"))
       +        self.update()
       +
       +    def do_broadcast(self):
       +        self.app.broadcast(self.tx)
       +
       +    def show_qr(self):
       +        from electrum.bitcoin import base_encode, bfh
       +        text = bfh(str(self.tx))
       +        text = base_encode(text, base=43)
       +        self.app.qr_dialog(_("Raw Transaction"), text)
   DIR diff --git a/gui/kivy/uix/dialogs/wallets.py b/electrum/gui/kivy/uix/dialogs/wallets.py
   DIR diff --git a/gui/kivy/uix/drawer.py b/electrum/gui/kivy/uix/drawer.py
   DIR diff --git a/gui/kivy/uix/gridview.py b/electrum/gui/kivy/uix/gridview.py
   DIR diff --git a/electrum/gui/kivy/uix/menus.py b/electrum/gui/kivy/uix/menus.py
       t@@ -0,0 +1,95 @@
       +from functools import partial
       +
       +from kivy.animation import Animation
       +from kivy.core.window import Window
       +from kivy.clock import Clock
       +from kivy.uix.bubble import Bubble, BubbleButton
       +from kivy.properties import ListProperty
       +from kivy.uix.widget import Widget
       +
       +from ..i18n import _
       +
       +class ContextMenuItem(Widget):
       +    '''abstract class
       +    '''
       +
       +class ContextButton(ContextMenuItem, BubbleButton):
       +    pass
       +
       +class ContextMenu(Bubble):
       +
       +    buttons = ListProperty([_('ok'), _('cancel')])
       +    '''List of Buttons to be displayed at the bottom'''
       +
       +    __events__ = ('on_press', 'on_release')
       +
       +    def __init__(self, **kwargs):
       +        self._old_buttons = self.buttons
       +        super(ContextMenu, self).__init__(**kwargs)
       +        self.on_buttons(self, self.buttons)
       +
       +    def on_touch_down(self, touch):
       +        if not self.collide_point(*touch.pos):
       +            self.hide()
       +            return
       +        return super(ContextMenu, self).on_touch_down(touch)
       +
       +    def on_buttons(self, _menu, value):
       +        if 'menu_content' not in self.ids.keys():
       +            return
       +        if value == self._old_buttons:
       +            return
       +        blayout = self.ids.menu_content
       +        blayout.clear_widgets()
       +        for btn in value:
       +            ib = ContextButton(text=btn)
       +            ib.bind(on_press=partial(self.dispatch, 'on_press'))
       +            ib.bind(on_release=partial(self.dispatch, 'on_release'))
       +            blayout.add_widget(ib)
       +        self._old_buttons = value
       +
       +    def on_press(self, instance):
       +        pass
       +
       +    def on_release(self, instance):
       +        pass
       +
       +    def show(self, pos, duration=0):
       +        Window.add_widget(self)
       +        # wait for the bubble to adjust it's size according to text then animate
       +        Clock.schedule_once(lambda dt: self._show(pos, duration))
       +
       +    def _show(self, pos, duration):
       +        def on_stop(*l):
       +            if duration:
       +                Clock.schedule_once(self.hide, duration + .5)
       +
       +        self.opacity = 0
       +        arrow_pos = self.arrow_pos
       +        if arrow_pos[0] in ('l', 'r'):
       +            pos = pos[0], pos[1] - (self.height/2)
       +        else:
       +            pos = pos[0] - (self.width/2), pos[1]
       +
       +        self.limit_to = Window
       +
       +        anim = Animation(opacity=1, pos=pos, d=.32)
       +        anim.bind(on_complete=on_stop)
       +        anim.cancel_all(self)
       +        anim.start(self)
       +
       +
       +    def hide(self, *dt):
       +
       +        def on_stop(*l):
       +            Window.remove_widget(self)
       +        anim = Animation(opacity=0, d=.25)
       +        anim.bind(on_complete=on_stop)
       +        anim.cancel_all(self)
       +        anim.start(self)
       +
       +    def add_widget(self, widget, index=0):
       +        if not isinstance(widget, ContextMenuItem):
       +            super(ContextMenu, self).add_widget(widget, index)
       +            return
       +        menu_content.add_widget(widget, index)
   DIR diff --git a/gui/kivy/uix/qrcodewidget.py b/electrum/gui/kivy/uix/qrcodewidget.py
   DIR diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py
       t@@ -0,0 +1,484 @@
       +from weakref import ref
       +from decimal import Decimal
       +import re
       +import datetime
       +import traceback, sys
       +
       +from kivy.app import App
       +from kivy.cache import Cache
       +from kivy.clock import Clock
       +from kivy.compat import string_types
       +from kivy.properties import (ObjectProperty, DictProperty, NumericProperty,
       +                             ListProperty, StringProperty)
       +
       +from kivy.uix.recycleview import RecycleView
       +from kivy.uix.label import Label
       +
       +from kivy.lang import Builder
       +from kivy.factory import Factory
       +from kivy.utils import platform
       +
       +from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
       +from electrum import bitcoin
       +from electrum.util import timestamp_to_datetime
       +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
       +from electrum.plugin import run_hook
       +
       +from .context_menu import ContextMenu
       +
       +
       +from electrum.gui.kivy.i18n import _
       +
       +class HistoryRecycleView(RecycleView):
       +    pass
       +
       +class CScreen(Factory.Screen):
       +    __events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')
       +    action_view = ObjectProperty(None)
       +    loaded = False
       +    kvname = None
       +    context_menu = None
       +    menu_actions = []
       +    app = App.get_running_app()
       +
       +    def _change_action_view(self):
       +        app = App.get_running_app()
       +        action_bar = app.root.manager.current_screen.ids.action_bar
       +        _action_view = self.action_view
       +
       +        if (not _action_view) or _action_view.parent:
       +            return
       +        action_bar.clear_widgets()
       +        action_bar.add_widget(_action_view)
       +
       +    def on_enter(self):
       +        # FIXME: use a proper event don't use animation time of screen
       +        Clock.schedule_once(lambda dt: self.dispatch('on_activate'), .25)
       +        pass
       +
       +    def update(self):
       +        pass
       +
       +    @profiler
       +    def load_screen(self):
       +        self.screen = Builder.load_file('electrum/gui/kivy/uix/ui_screens/' + self.kvname + '.kv')
       +        self.add_widget(self.screen)
       +        self.loaded = True
       +        self.update()
       +        setattr(self.app, self.kvname + '_screen', self)
       +
       +    def on_activate(self):
       +        if self.kvname and not self.loaded:
       +            self.load_screen()
       +        #Clock.schedule_once(lambda dt: self._change_action_view())
       +
       +    def on_leave(self):
       +        self.dispatch('on_deactivate')
       +
       +    def on_deactivate(self):
       +        self.hide_menu()
       +
       +    def hide_menu(self):
       +        if self.context_menu is not None:
       +            self.remove_widget(self.context_menu)
       +            self.context_menu = None
       +
       +    def show_menu(self, obj):
       +        self.hide_menu()
       +        self.context_menu = ContextMenu(obj, self.menu_actions)
       +        self.add_widget(self.context_menu)
       +
       +
       +# note: this list needs to be kept in sync with another in qt
       +TX_ICONS = [
       +    "unconfirmed",
       +    "close",
       +    "unconfirmed",
       +    "close",
       +    "clock1",
       +    "clock2",
       +    "clock3",
       +    "clock4",
       +    "clock5",
       +    "confirmed",
       +]
       +
       +class HistoryScreen(CScreen):
       +
       +    tab = ObjectProperty(None)
       +    kvname = 'history'
       +    cards = {}
       +
       +    def __init__(self, **kwargs):
       +        self.ra_dialog = None
       +        super(HistoryScreen, self).__init__(**kwargs)
       +        self.menu_actions = [ ('Label', self.label_dialog), ('Details', self.show_tx)]
       +
       +    def show_tx(self, obj):
       +        tx_hash = obj.tx_hash
       +        tx = self.app.wallet.transactions.get(tx_hash)
       +        if not tx:
       +            return
       +        self.app.tx_dialog(tx)
       +
       +    def label_dialog(self, obj):
       +        from .dialogs.label_dialog import LabelDialog
       +        key = obj.tx_hash
       +        text = self.app.wallet.get_label(key)
       +        def callback(text):
       +            self.app.wallet.set_label(key, text)
       +            self.update()
       +        d = LabelDialog(_('Enter Transaction Label'), text, callback)
       +        d.open()
       +
       +    def get_card(self, tx_hash, height, conf, timestamp, value, balance):
       +        status, status_str = self.app.wallet.get_tx_status(tx_hash, height, conf, timestamp)
       +        icon = "atlas://electrum/gui/kivy/theming/light/" + TX_ICONS[status]
       +        label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
       +        ri = {}
       +        ri['screen'] = self
       +        ri['tx_hash'] = tx_hash
       +        ri['icon'] = icon
       +        ri['date'] = status_str
       +        ri['message'] = label
       +        ri['confirmations'] = conf
       +        if value is not None:
       +            ri['is_mine'] = value < 0
       +            if value < 0: value = - value
       +            ri['amount'] = self.app.format_amount_and_units(value)
       +            if self.app.fiat_unit:
       +                fx = self.app.fx
       +                fiat_value = value / Decimal(bitcoin.COIN) * self.app.wallet.price_at_timestamp(tx_hash, fx.timestamp_rate)
       +                fiat_value = Fiat(fiat_value, fx.ccy)
       +                ri['quote_text'] = str(fiat_value)
       +        return ri
       +
       +    def update(self, see_all=False):
       +        if self.app.wallet is None:
       +            return
       +        history = reversed(self.app.wallet.get_history())
       +        history_card = self.screen.ids.history_container
       +        count = 0
       +        history_card.data = [self.get_card(*item) for item in history]
       +
       +
       +class SendScreen(CScreen):
       +
       +    kvname = 'send'
       +    payment_request = None
       +    payment_request_queued = None
       +
       +    def set_URI(self, text):
       +        if not self.app.wallet:
       +            self.payment_request_queued = text
       +            return
       +        import electrum
       +        try:
       +            uri = electrum.util.parse_URI(text, self.app.on_pr)
       +        except:
       +            self.app.show_info(_("Not a Bitcoin URI"))
       +            return
       +        amount = uri.get('amount')
       +        self.screen.address = uri.get('address', '')
       +        self.screen.message = uri.get('message', '')
       +        self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
       +        self.payment_request = None
       +        self.screen.is_pr = False
       +
       +    def update(self):
       +        if self.app.wallet and self.payment_request_queued:
       +            self.set_URI(self.payment_request_queued)
       +            self.payment_request_queued = None
       +
       +    def do_clear(self):
       +        self.screen.amount = ''
       +        self.screen.message = ''
       +        self.screen.address = ''
       +        self.payment_request = None
       +        self.screen.is_pr = False
       +
       +    def set_request(self, pr):
       +        self.screen.address = pr.get_requestor()
       +        amount = pr.get_amount()
       +        self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
       +        self.screen.message = pr.get_memo()
       +        if pr.is_pr():
       +            self.screen.is_pr = True
       +            self.payment_request = pr
       +        else:
       +            self.screen.is_pr = False
       +            self.payment_request = None
       +
       +    def do_save(self):
       +        if not self.screen.address:
       +            return
       +        if self.screen.is_pr:
       +            # it should be already saved
       +            return
       +        # save address as invoice
       +        from electrum.paymentrequest import make_unsigned_request, PaymentRequest
       +        req = {'address':self.screen.address, 'memo':self.screen.message}
       +        amount = self.app.get_amount(self.screen.amount) if self.screen.amount else 0
       +        req['amount'] = amount
       +        pr = make_unsigned_request(req).SerializeToString()
       +        pr = PaymentRequest(pr)
       +        self.app.wallet.invoices.add(pr)
       +        self.app.show_info(_("Invoice saved"))
       +        if pr.is_pr():
       +            self.screen.is_pr = True
       +            self.payment_request = pr
       +        else:
       +            self.screen.is_pr = False
       +            self.payment_request = None
       +
       +    def do_paste(self):
       +        contents = self.app._clipboard.paste()
       +        if not contents:
       +            self.app.show_info(_("Clipboard is empty"))
       +            return
       +        self.set_URI(contents)
       +
       +    def do_send(self):
       +        if self.screen.is_pr:
       +            if self.payment_request.has_expired():
       +                self.app.show_error(_('Payment request has expired'))
       +                return
       +            outputs = self.payment_request.get_outputs()
       +        else:
       +            address = str(self.screen.address)
       +            if not address:
       +                self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a Bitcoin address or a payment request'))
       +                return
       +            if not bitcoin.is_address(address):
       +                self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
       +                return
       +            try:
       +                amount = self.app.get_amount(self.screen.amount)
       +            except:
       +                self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
       +                return
       +            outputs = [(bitcoin.TYPE_ADDRESS, address, amount)]
       +        message = self.screen.message
       +        amount = sum(map(lambda x:x[2], outputs))
       +        if self.app.electrum_config.get('use_rbf'):
       +            from .dialogs.question import Question
       +            d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b))
       +            d.open()
       +        else:
       +            self._do_send(amount, message, outputs, False)
       +
       +    def _do_send(self, amount, message, outputs, rbf):
       +        # make unsigned transaction
       +        config = self.app.electrum_config
       +        coins = self.app.wallet.get_spendable_coins(None, config)
       +        try:
       +            tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None)
       +        except NotEnoughFunds:
       +            self.app.show_error(_("Not enough funds"))
       +            return
       +        except Exception as e:
       +            traceback.print_exc(file=sys.stdout)
       +            self.app.show_error(str(e))
       +            return
       +        if rbf:
       +            tx.set_rbf(True)
       +        fee = tx.get_fee()
       +        msg = [
       +            _("Amount to be sent") + ": " + self.app.format_amount_and_units(amount),
       +            _("Mining fee") + ": " + self.app.format_amount_and_units(fee),
       +        ]
       +        x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
       +        if x_fee:
       +            x_fee_address, x_fee_amount = x_fee
       +            msg.append(_("Additional fees") + ": " + self.app.format_amount_and_units(x_fee_amount))
       +
       +        if fee >= config.get('confirm_fee', 100000):
       +            msg.append(_('Warning')+ ': ' + _("The fee for this transaction seems unusually high."))
       +        msg.append(_("Enter your PIN code to proceed"))
       +        self.app.protected('\n'.join(msg), self.send_tx, (tx, message))
       +
       +    def send_tx(self, tx, message, password):
       +        if self.app.wallet.has_password() and password is None:
       +            return
       +        def on_success(tx):
       +            if tx.is_complete():
       +                self.app.broadcast(tx, self.payment_request)
       +                self.app.wallet.set_label(tx.txid(), message)
       +            else:
       +                self.app.tx_dialog(tx)
       +        def on_failure(error):
       +            self.app.show_error(error)
       +        if self.app.wallet.can_sign(tx):
       +            self.app.show_info("Signing...")
       +            self.app.sign_tx(tx, password, on_success, on_failure)
       +        else:
       +            self.app.tx_dialog(tx)
       +
       +
       +class ReceiveScreen(CScreen):
       +
       +    kvname = 'receive'
       +
       +    def update(self):
       +        if not self.screen.address:
       +            self.get_new_address()
       +        else:
       +            status = self.app.wallet.get_request_status(self.screen.address)
       +            self.screen.status = _('Payment received') if status == PR_PAID else ''
       +
       +    def clear(self):
       +        self.screen.address = ''
       +        self.screen.amount = ''
       +        self.screen.message = ''
       +
       +    def get_new_address(self):
       +        if not self.app.wallet:
       +            return False
       +        self.clear()
       +        addr = self.app.wallet.get_unused_address()
       +        if addr is None:
       +            addr = self.app.wallet.get_receiving_address() or ''
       +            b = False
       +        else:
       +            b = True
       +        self.screen.address = addr
       +        return b
       +
       +    def on_address(self, addr):
       +        req = self.app.wallet.get_payment_request(addr, self.app.electrum_config)
       +        self.screen.status = ''
       +        if req:
       +            self.screen.message = req.get('memo', '')
       +            amount = req.get('amount')
       +            self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
       +            status = req.get('status', PR_UNKNOWN)
       +            self.screen.status = _('Payment received') if status == PR_PAID else ''
       +        Clock.schedule_once(lambda dt: self.update_qr())
       +
       +    def get_URI(self):
       +        from electrum.util import create_URI
       +        amount = self.screen.amount
       +        if amount:
       +            a, u = self.screen.amount.split()
       +            assert u == self.app.base_unit
       +            amount = Decimal(a) * pow(10, self.app.decimal_point())
       +        return create_URI(self.screen.address, amount, self.screen.message)
       +
       +    @profiler
       +    def update_qr(self):
       +        uri = self.get_URI()
       +        qr = self.screen.ids.qr
       +        qr.set_data(uri)
       +
       +    def do_share(self):
       +        uri = self.get_URI()
       +        self.app.do_share(uri, _("Share Bitcoin Request"))
       +
       +    def do_copy(self):
       +        uri = self.get_URI()
       +        self.app._clipboard.copy(uri)
       +        self.app.show_info(_('Request copied to clipboard'))
       +
       +    def save_request(self):
       +        addr = self.screen.address
       +        if not addr:
       +            return False
       +        amount = self.screen.amount
       +        message = self.screen.message
       +        amount = self.app.get_amount(amount) if amount else 0
       +        req = self.app.wallet.make_payment_request(addr, amount, message, None)
       +        try:
       +            self.app.wallet.add_payment_request(req, self.app.electrum_config)
       +            added_request = True
       +        except Exception as e:
       +            self.app.show_error(_('Error adding payment request') + ':\n' + str(e))
       +            added_request = False
       +        finally:
       +            self.app.update_tab('requests')
       +        return added_request
       +
       +    def on_amount_or_message(self):
       +        Clock.schedule_once(lambda dt: self.update_qr())
       +
       +    def do_new(self):
       +        addr = self.get_new_address()
       +        if not addr:
       +            self.app.show_info(_('Please use the existing requests first.'))
       +
       +    def do_save(self):
       +        if self.save_request():
       +            self.app.show_info(_('Request was saved.'))
       +
       +
       +class TabbedCarousel(Factory.TabbedPanel):
       +    '''Custom TabbedPanel using a carousel used in the Main Screen
       +    '''
       +
       +    carousel = ObjectProperty(None)
       +
       +    def animate_tab_to_center(self, value):
       +        scrlv = self._tab_strip.parent
       +        if not scrlv:
       +            return
       +        idx = self.tab_list.index(value)
       +        n = len(self.tab_list)
       +        if idx in [0, 1]:
       +            scroll_x = 1
       +        elif idx in [n-1, n-2]:
       +            scroll_x = 0
       +        else:
       +            scroll_x = 1. * (n - idx - 1) / (n - 1)
       +        mation = Factory.Animation(scroll_x=scroll_x, d=.25)
       +        mation.cancel_all(scrlv)
       +        mation.start(scrlv)
       +
       +    def on_current_tab(self, instance, value):
       +        self.animate_tab_to_center(value)
       +
       +    def on_index(self, instance, value):
       +        current_slide = instance.current_slide
       +        if not hasattr(current_slide, 'tab'):
       +            return
       +        tab = current_slide.tab
       +        ct = self.current_tab
       +        try:
       +            if ct.text != tab.text:
       +                carousel = self.carousel
       +                carousel.slides[ct.slide].dispatch('on_leave')
       +                self.switch_to(tab)
       +                carousel.slides[tab.slide].dispatch('on_enter')
       +        except AttributeError:
       +            current_slide.dispatch('on_enter')
       +
       +    def switch_to(self, header):
       +        # we have to replace the functionality of the original switch_to
       +        if not header:
       +            return
       +        if not hasattr(header, 'slide'):
       +            header.content = self.carousel
       +            super(TabbedCarousel, self).switch_to(header)
       +            try:
       +                tab = self.tab_list[-1]
       +            except IndexError:
       +                return
       +            self._current_tab = tab
       +            tab.state = 'down'
       +            return
       +
       +        carousel = self.carousel
       +        self.current_tab.state = "normal"
       +        header.state = 'down'
       +        self._current_tab = header
       +        # set the carousel to load the appropriate slide
       +        # saved in the screen attribute of the tab head
       +        slide = carousel.slides[header.slide]
       +        if carousel.current_slide != slide:
       +            carousel.current_slide.dispatch('on_leave')
       +            carousel.load_slide(slide)
       +            slide.dispatch('on_enter')
       +
       +    def add_widget(self, widget, index=0):
       +        if isinstance(widget, Factory.CScreen):
       +            self.carousel.add_widget(widget)
       +            return
       +        super(TabbedCarousel, self).add_widget(widget, index=index)
   DIR diff --git a/gui/kivy/uix/ui_screens/about.kv b/electrum/gui/kivy/uix/ui_screens/about.kv
   DIR diff --git a/electrum/gui/kivy/uix/ui_screens/history.kv b/electrum/gui/kivy/uix/ui_screens/history.kv
       t@@ -0,0 +1,78 @@
       +#:import _ electrum.gui.kivy.i18n._
       +#:import Factory kivy.factory.Factory
       +#:set font_light 'electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf'
       +#:set btc_symbol chr(171)
       +#:set mbtc_symbol chr(187)
       +
       +
       +
       +<CardLabel@Label>
       +    color: 0.95, 0.95, 0.95, 1
       +    size_hint: 1, None
       +    text: ''
       +    text_size: self.width, None
       +    height: self.texture_size[1]
       +    halign: 'left'
       +    valign: 'top'
       +
       +
       +<HistoryItem@CardItem>
       +    icon: 'atlas://electrum/gui/kivy/theming/light/important'
       +    message: ''
       +    is_mine: True
       +    amount: '--'
       +    action: _('Sent') if self.is_mine else _('Received')
       +    amount_color: '#FF6657' if self.is_mine else '#2EA442'
       +    confirmations: 0
       +    date: ''
       +    quote_text: ''
       +    Image:
       +        id: icon
       +        source: root.icon
       +        size_hint: None, 1
       +        allow_stretch: True
       +        width: self.height*1.5
       +        mipmap: True
       +    BoxLayout:
       +        orientation: 'vertical'
       +        Widget
       +        CardLabel:
       +            text:
       +                u'[color={color}]{s}[/color]'.format(s='<<' if root.is_mine else '>>', color=root.amount_color)\
       +                + ' ' + root.action + ' ' + (root.quote_text if app.is_fiat else root.amount)
       +            font_size: '15sp'
       +        CardLabel:
       +            color: .699, .699, .699, 1
       +            font_size: '14sp'
       +            shorten: True
       +            text: root.date + '   ' + root.message
       +        Widget
       +
       +<HistoryRecycleView>:
       +    viewclass: 'HistoryItem'
       +    RecycleBoxLayout:
       +        default_size: None, dp(56)
       +        default_size_hint: 1, None
       +        size_hint: 1, None
       +        height: self.minimum_height
       +        orientation: 'vertical'
       +
       +
       +HistoryScreen:
       +    name: 'history'
       +    content: history_container
       +    BoxLayout:
       +        orientation: 'vertical'
       +        Button:
       +            background_color: 0, 0, 0, 0
       +            text: app.fiat_balance if app.is_fiat else app.balance
       +            markup: True
       +            color: .9, .9, .9, 1
       +            font_size: '30dp'
       +            bold: True
       +            size_hint: 1, 0.25
       +            on_release: app.is_fiat = not app.is_fiat if app.fx.is_enabled() else False
       +        HistoryRecycleView:
       +            id: history_container
       +            scroll_type: ['bars', 'content']
       +            bar_width: '25dp'
   DIR diff --git a/gui/kivy/uix/ui_screens/invoice.kv b/electrum/gui/kivy/uix/ui_screens/invoice.kv
   DIR diff --git a/gui/kivy/uix/ui_screens/network.kv b/electrum/gui/kivy/uix/ui_screens/network.kv
   DIR diff --git a/gui/kivy/uix/ui_screens/proxy.kv b/electrum/gui/kivy/uix/ui_screens/proxy.kv
   DIR diff --git a/electrum/gui/kivy/uix/ui_screens/receive.kv b/electrum/gui/kivy/uix/ui_screens/receive.kv
       t@@ -0,0 +1,142 @@
       +#:import _ electrum.gui.kivy.i18n._
       +#:import Decimal decimal.Decimal
       +#:set btc_symbol chr(171)
       +#:set mbtc_symbol chr(187)
       +#:set font_light 'electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf'
       +
       +
       +
       +ReceiveScreen:
       +    id: s
       +    name: 'receive'
       +
       +    address: ''
       +    amount: ''
       +    message: ''
       +    status: ''
       +
       +    on_address:
       +        self.parent.on_address(self.address)
       +    on_amount:
       +        self.parent.on_amount_or_message()
       +    on_message:
       +        self.parent.on_amount_or_message()
       +
       +    BoxLayout
       +        padding: '12dp', '12dp', '12dp', '12dp'
       +        spacing: '12dp'
       +        orientation: 'vertical'
       +        size_hint: 1, 1
       +        FloatLayout:
       +            id: bl
       +            QRCodeWidget:
       +                id: qr
       +                size_hint: None, 1
       +                width: min(self.height, bl.width)
       +                pos_hint: {'center': (.5, .5)}
       +                shaded: False
       +                foreground_color: (0, 0, 0, 0.5) if self.shaded else (0, 0, 0, 0)
       +                on_touch_down:
       +                    touch = args[1]
       +                    if self.collide_point(*touch.pos): self.shaded = not self.shaded
       +            Label:
       +                text: root.status
       +                opacity: 1 if root.status else 0
       +                pos_hint: {'center': (.5, .5)}
       +                size_hint: None, 1
       +                width: min(self.height, bl.width)
       +                bcolor: 0.3, 0.3, 0.3, 0.9
       +                canvas.before:
       +                    Color:
       +                        rgba: self.bcolor
       +                    Rectangle:
       +                        pos: self.pos
       +                        size: self.size
       +
       +        SendReceiveBlueBottom:
       +            id: blue_bottom
       +            size_hint: 1, None
       +            height: self.minimum_height
       +            BoxLayout:
       +                size_hint: 1, None
       +                height: blue_bottom.item_height
       +                spacing: '5dp'
       +                Image:
       +                    source: 'atlas://electrum/gui/kivy/theming/light/globe'
       +                    size_hint: None, None
       +                    size: '22dp', '22dp'
       +                    pos_hint: {'center_y': .5}
       +                BlueButton:
       +                    id: address_label
       +                    text: s.address if s.address else _('Bitcoin Address')
       +                    shorten: True
       +                    on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s))
       +            CardSeparator:
       +                opacity: message_selection.opacity
       +                color: blue_bottom.foreground_color
       +            BoxLayout:
       +                size_hint: 1, None
       +                height: blue_bottom.item_height
       +                spacing: '5dp'
       +                Image:
       +                    source: 'atlas://electrum/gui/kivy/theming/light/calculator'
       +                    opacity: 0.7
       +                    size_hint: None, None
       +                    size: '22dp', '22dp'
       +                    pos_hint: {'center_y': .5}
       +                BlueButton:
       +                    id: amount_label
       +                    default_text: _('Amount')
       +                    text: s.amount if s.amount else _('Amount')
       +                    on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, False))
       +            CardSeparator:
       +                opacity: message_selection.opacity
       +                color: blue_bottom.foreground_color
       +            BoxLayout:
       +                id: message_selection
       +                opacity: 1
       +                size_hint: 1, None
       +                height: blue_bottom.item_height
       +                spacing: '5dp'
       +                Image:
       +                    source: 'atlas://electrum/gui/kivy/theming/light/pen'
       +                    size_hint: None, None
       +                    size: '22dp', '22dp'
       +                    pos_hint: {'center_y': .5}
       +                BlueButton:
       +                    id: description
       +                    text: s.message if s.message else _('Description')
       +                    on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
       +        BoxLayout:
       +            size_hint: 1, None
       +            height: '48dp'
       +            IconButton:
       +                icon: 'atlas://electrum/gui/kivy/theming/light/save'
       +                size_hint: 0.6, None
       +                height: '48dp'
       +                on_release: s.parent.do_save()
       +            Button:
       +                text: _('Requests')
       +                size_hint: 1, None
       +                height: '48dp'
       +                on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s))
       +            Button:
       +                text: _('Copy')
       +                size_hint: 1, None
       +                height: '48dp'
       +                on_release: s.parent.do_copy()
       +            IconButton:
       +                icon: 'atlas://electrum/gui/kivy/theming/light/share'
       +                size_hint: 0.6, None
       +                height: '48dp'
       +                on_release: s.parent.do_share()
       +        BoxLayout:
       +            size_hint: 1, None
       +            height: '48dp'
       +            Widget
       +                size_hint: 2, 1
       +            Button:
       +                text: _('New')
       +                size_hint: 1, None
       +                height: '48dp'
       +                on_release: Clock.schedule_once(lambda dt: s.parent.do_new())
   DIR diff --git a/electrum/gui/kivy/uix/ui_screens/send.kv b/electrum/gui/kivy/uix/ui_screens/send.kv
       t@@ -0,0 +1,127 @@
       +#:import _ electrum.gui.kivy.i18n._
       +#:import Decimal decimal.Decimal
       +#:set btc_symbol chr(171)
       +#:set mbtc_symbol chr(187)
       +#:set font_light 'electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf'
       +
       +
       +SendScreen:
       +    id: s
       +    name: 'send'
       +    address: ''
       +    amount: ''
       +    message: ''
       +    is_pr: False
       +    BoxLayout
       +        padding: '12dp', '12dp', '12dp', '12dp'
       +        spacing: '12dp'
       +        orientation: 'vertical'
       +        SendReceiveBlueBottom:
       +            id: blue_bottom
       +            size_hint: 1, None
       +            height: self.minimum_height
       +            BoxLayout:
       +                size_hint: 1, None
       +                height: blue_bottom.item_height
       +                spacing: '5dp'
       +                Image:
       +                    source: 'atlas://electrum/gui/kivy/theming/light/globe'
       +                    size_hint: None, None
       +                    size: '22dp', '22dp'
       +                    pos_hint: {'center_y': .5}
       +                BlueButton:
       +                    id: payto_e
       +                    text: s.address if s.address else _('Recipient')
       +                    shorten: True
       +                    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'))
       +            CardSeparator:
       +                opacity: int(not root.is_pr)
       +                color: blue_bottom.foreground_color
       +            BoxLayout:
       +                size_hint: 1, None
       +                height: blue_bottom.item_height
       +                spacing: '5dp'
       +                Image:
       +                    source: 'atlas://electrum/gui/kivy/theming/light/calculator'
       +                    opacity: 0.7
       +                    size_hint: None, None
       +                    size: '22dp', '22dp'
       +                    pos_hint: {'center_y': .5}
       +                BlueButton:
       +                    id: amount_e
       +                    default_text: _('Amount')
       +                    text: s.amount if s.amount else _('Amount')
       +                    disabled: root.is_pr
       +                    on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, True))
       +            CardSeparator:
       +                opacity: int(not root.is_pr)
       +                color: blue_bottom.foreground_color
       +            BoxLayout:
       +                id: message_selection
       +                size_hint: 1, None
       +                height: blue_bottom.item_height
       +                spacing: '5dp'
       +                Image:
       +                    source: 'atlas://electrum/gui/kivy/theming/light/pen'
       +                    size_hint: None, None
       +                    size: '22dp', '22dp'
       +                    pos_hint: {'center_y': .5}
       +                BlueButton:
       +                    id: description
       +                    text: s.message if s.message else (_('No Description') if root.is_pr else _('Description'))
       +                    disabled: root.is_pr
       +                    on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
       +            CardSeparator:
       +                opacity: int(not root.is_pr)
       +                color: blue_bottom.foreground_color
       +            BoxLayout:
       +                size_hint: 1, None
       +                height: blue_bottom.item_height
       +                spacing: '5dp'
       +                Image:
       +                    source: 'atlas://electrum/gui/kivy/theming/light/star_big_inactive'
       +                    opacity: 0.7
       +                    size_hint: None, None
       +                    size: '22dp', '22dp'
       +                    pos_hint: {'center_y': .5}
       +                BlueButton:
       +                    id: fee_e
       +                    default_text: _('Fee')
       +                    text: app.fee_status
       +                    on_release: Clock.schedule_once(lambda dt: app.fee_dialog(s, True))
       +        BoxLayout:
       +            size_hint: 1, None
       +            height: '48dp'
       +            IconButton:
       +                size_hint: 0.6, 1
       +                on_release: s.parent.do_save()
       +                icon: 'atlas://electrum/gui/kivy/theming/light/save'
       +            Button:
       +                text: _('Invoices')
       +                size_hint: 1, 1
       +                on_release: Clock.schedule_once(lambda dt: app.invoices_dialog(s))
       +            Button:
       +                text: _('Paste')
       +                on_release: s.parent.do_paste()
       +            IconButton:
       +                id: qr
       +                size_hint: 0.6, 1
       +                on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr))
       +                icon: 'atlas://electrum/gui/kivy/theming/light/camera'
       +        BoxLayout:
       +            size_hint: 1, None
       +            height: '48dp'
       +            Button:
       +                text: _('Clear')
       +                on_release: s.parent.do_clear()
       +            Widget:
       +                size_hint: 1, 1
       +            Button:
       +                text: _('Pay')
       +                size_hint: 1, 1
       +                on_release: s.parent.do_send()
       +        Widget:
       +            size_hint: 1, 1
       +
       +
   DIR diff --git a/gui/kivy/uix/ui_screens/server.kv b/electrum/gui/kivy/uix/ui_screens/server.kv
   DIR diff --git a/gui/kivy/uix/ui_screens/status.kv b/electrum/gui/kivy/uix/ui_screens/status.kv
   DIR diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py
       t@@ -0,0 +1,313 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2012 thomasv@gitorious
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +import signal
       +import sys
       +import traceback
       +
       +
       +try:
       +    import PyQt5
       +except Exception:
       +    sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'")
       +
       +from PyQt5.QtGui import *
       +from PyQt5.QtWidgets import *
       +from PyQt5.QtCore import *
       +import PyQt5.QtCore as QtCore
       +
       +from electrum.i18n import _, set_language
       +from electrum.plugin import run_hook
       +from electrum.storage import WalletStorage
       +from electrum.base_wizard import GoBack
       +# from electrum.synchronizer import Synchronizer
       +# from electrum.verifier import SPV
       +# from electrum.util import DebugMem
       +from electrum.util import (UserCancelled, print_error,
       +                           WalletFileException, BitcoinException)
       +# from electrum.wallet import Abstract_Wallet
       +
       +from .installwizard import InstallWizard
       +
       +
       +try:
       +    from . import icons_rc
       +except Exception as e:
       +    print(e)
       +    print("Error: Could not find icons file.")
       +    print("Please run 'pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py'")
       +    sys.exit(1)
       +
       +from .util import *   # * needed for plugins
       +from .main_window import ElectrumWindow
       +from .network_dialog import NetworkDialog
       +
       +
       +class OpenFileEventFilter(QObject):
       +    def __init__(self, windows):
       +        self.windows = windows
       +        super(OpenFileEventFilter, self).__init__()
       +
       +    def eventFilter(self, obj, event):
       +        if event.type() == QtCore.QEvent.FileOpen:
       +            if len(self.windows) >= 1:
       +                self.windows[0].pay_to_URI(event.url().toEncoded())
       +                return True
       +        return False
       +
       +
       +class QElectrumApplication(QApplication):
       +    new_window_signal = pyqtSignal(str, object)
       +
       +
       +class QNetworkUpdatedSignalObject(QObject):
       +    network_updated_signal = pyqtSignal(str, object)
       +
       +
       +class ElectrumGui:
       +
       +    def __init__(self, config, daemon, plugins):
       +        set_language(config.get('language'))
       +        # Uncomment this call to verify objects are being properly
       +        # GC-ed when windows are closed
       +        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
       +        #                            ElectrumWindow], interval=5)])
       +        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
       +        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
       +            QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
       +        if hasattr(QGuiApplication, 'setDesktopFileName'):
       +            QGuiApplication.setDesktopFileName('electrum.desktop')
       +        self.config = config
       +        self.daemon = daemon
       +        self.plugins = plugins
       +        self.windows = []
       +        self.efilter = OpenFileEventFilter(self.windows)
       +        self.app = QElectrumApplication(sys.argv)
       +        self.app.installEventFilter(self.efilter)
       +        self.timer = Timer()
       +        self.nd = None
       +        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
       +        # init tray
       +        self.dark_icon = self.config.get("dark_icon", False)
       +        self.tray = QSystemTrayIcon(self.tray_icon(), None)
       +        self.tray.setToolTip('Electrum')
       +        self.tray.activated.connect(self.tray_activated)
       +        self.build_tray_menu()
       +        self.tray.show()
       +        self.app.new_window_signal.connect(self.start_new_window)
       +        self.set_dark_theme_if_needed()
       +        run_hook('init_qt', self)
       +
       +    def set_dark_theme_if_needed(self):
       +        use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark'
       +        if use_dark_theme:
       +            try:
       +                import qdarkstyle
       +                self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
       +            except BaseException as e:
       +                use_dark_theme = False
       +                print_error('Error setting dark theme: {}'.format(e))
       +        # Even if we ourselves don't set the dark theme,
       +        # the OS/window manager/etc might set *a dark theme*.
       +        # Hence, try to choose colors accordingly:
       +        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)
       +
       +    def build_tray_menu(self):
       +        # Avoid immediate GC of old menu when window closed via its action
       +        if self.tray.contextMenu() is None:
       +            m = QMenu()
       +            self.tray.setContextMenu(m)
       +        else:
       +            m = self.tray.contextMenu()
       +            m.clear()
       +        for window in self.windows:
       +            submenu = m.addMenu(window.wallet.basename())
       +            submenu.addAction(_("Show/Hide"), window.show_or_hide)
       +            submenu.addAction(_("Close"), window.close)
       +        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
       +        m.addSeparator()
       +        m.addAction(_("Exit Electrum"), self.close)
       +
       +    def tray_icon(self):
       +        if self.dark_icon:
       +            return QIcon(':icons/electrum_dark_icon.png')
       +        else:
       +            return QIcon(':icons/electrum_light_icon.png')
       +
       +    def toggle_tray_icon(self):
       +        self.dark_icon = not self.dark_icon
       +        self.config.set_key("dark_icon", self.dark_icon, True)
       +        self.tray.setIcon(self.tray_icon())
       +
       +    def tray_activated(self, reason):
       +        if reason == QSystemTrayIcon.DoubleClick:
       +            if all([w.is_hidden() for w in self.windows]):
       +                for w in self.windows:
       +                    w.bring_to_top()
       +            else:
       +                for w in self.windows:
       +                    w.hide()
       +
       +    def close(self):
       +        for window in self.windows:
       +            window.close()
       +
       +    def new_window(self, path, uri=None):
       +        # Use a signal as can be called from daemon thread
       +        self.app.new_window_signal.emit(path, uri)
       +
       +    def show_network_dialog(self, parent):
       +        if not self.daemon.network:
       +            parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
       +            return
       +        if self.nd:
       +            self.nd.on_update()
       +            self.nd.show()
       +            self.nd.raise_()
       +            return
       +        self.nd = NetworkDialog(self.daemon.network, self.config,
       +                                self.network_updated_signal_obj)
       +        self.nd.show()
       +
       +    def create_window_for_wallet(self, wallet):
       +        w = ElectrumWindow(self, wallet)
       +        self.windows.append(w)
       +        self.build_tray_menu()
       +        # FIXME: Remove in favour of the load_wallet hook
       +        run_hook('on_new_window', w)
       +        return w
       +
       +    def start_new_window(self, path, uri, app_is_starting=False):
       +        '''Raises the window for the wallet if it is open.  Otherwise
       +        opens the wallet and creates a new window for it'''
       +        try:
       +            wallet = self.daemon.load_wallet(path, None)
       +        except BaseException as e:
       +            traceback.print_exc(file=sys.stdout)
       +            d = QMessageBox(QMessageBox.Warning, _('Error'),
       +                            _('Cannot load wallet') + ' (1):\n' + str(e))
       +            d.exec_()
       +            if app_is_starting:
       +                # do not return so that the wizard can appear
       +                wallet = None
       +            else:
       +                return
       +        if not wallet:
       +            storage = WalletStorage(path, manual_upgrades=True)
       +            wizard = InstallWizard(self.config, self.app, self.plugins, storage)
       +            try:
       +                wallet = wizard.run_and_get_wallet(self.daemon.get_wallet)
       +            except UserCancelled:
       +                pass
       +            except GoBack as e:
       +                print_error('[start_new_window] Exception caught (GoBack)', e)
       +            except (WalletFileException, BitcoinException) as e:
       +                traceback.print_exc(file=sys.stderr)
       +                d = QMessageBox(QMessageBox.Warning, _('Error'),
       +                                _('Cannot load wallet') + ' (2):\n' + str(e))
       +                d.exec_()
       +                return
       +            finally:
       +                wizard.terminate()
       +            if not wallet:
       +                return
       +
       +            if not self.daemon.get_wallet(wallet.storage.path):
       +                # wallet was not in memory
       +                wallet.start_threads(self.daemon.network)
       +                self.daemon.add_wallet(wallet)
       +        try:
       +            for w in self.windows:
       +                if w.wallet.storage.path == wallet.storage.path:
       +                    w.bring_to_top()
       +                    return
       +            w = self.create_window_for_wallet(wallet)
       +        except BaseException as e:
       +            traceback.print_exc(file=sys.stdout)
       +            d = QMessageBox(QMessageBox.Warning, _('Error'),
       +                            _('Cannot create window for wallet') + ':\n' + str(e))
       +            d.exec_()
       +            return
       +        if uri:
       +            w.pay_to_URI(uri)
       +        w.bring_to_top()
       +        w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
       +
       +        # this will activate the window
       +        w.activateWindow()
       +        return w
       +
       +    def close_window(self, window):
       +        self.windows.remove(window)
       +        self.build_tray_menu()
       +        # save wallet path of last open window
       +        if not self.windows:
       +            self.config.save_last_wallet(window.wallet)
       +        run_hook('on_close_window', window)
       +
       +    def init_network(self):
       +        # Show network dialog if config does not exist
       +        if self.daemon.network:
       +            if self.config.get('auto_connect') is None:
       +                wizard = InstallWizard(self.config, self.app, self.plugins, None)
       +                wizard.init_network(self.daemon.network)
       +                wizard.terminate()
       +
       +    def main(self):
       +        try:
       +            self.init_network()
       +        except UserCancelled:
       +            return
       +        except GoBack:
       +            return
       +        except BaseException as e:
       +            traceback.print_exc(file=sys.stdout)
       +            return
       +        self.timer.start()
       +        self.config.open_last_wallet()
       +        path = self.config.get_wallet_path()
       +        if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
       +            return
       +        signal.signal(signal.SIGINT, lambda *args: self.app.quit())
       +
       +        def quit_after_last_window():
       +            # on some platforms, not only does exec_ not return but not even
       +            # aboutToQuit is emitted (but following this, it should be emitted)
       +            if self.app.quitOnLastWindowClosed():
       +                self.app.quit()
       +        self.app.lastWindowClosed.connect(quit_after_last_window)
       +
       +        def clean_up():
       +            # Shut down the timer cleanly
       +            self.timer.stop()
       +            # clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
       +            event = QtCore.QEvent(QtCore.QEvent.Clipboard)
       +            self.app.sendEvent(self.app.clipboard(), event)
       +            self.tray.hide()
       +        self.app.aboutToQuit.connect(clean_up)
       +
       +        # main loop
       +        self.app.exec_()
       +        # on some platforms the exec_ call may not return, so use clean_up()
   DIR diff --git a/gui/qt/address_dialog.py b/electrum/gui/qt/address_dialog.py
   DIR diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py
       t@@ -0,0 +1,195 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2015 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import webbrowser
       +
       +from electrum.i18n import _
       +from electrum.util import block_explorer_URL
       +from electrum.plugin import run_hook
       +from electrum.bitcoin import is_address
       +
       +from .util import *
       +
       +
       +class AddressList(MyTreeWidget):
       +    filter_columns = [0, 1, 2, 3]  # Type, Address, Label, Balance
       +
       +    def __init__(self, parent=None):
       +        MyTreeWidget.__init__(self, parent, self.create_menu, [], 2)
       +        self.refresh_headers()
       +        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
       +        self.setSortingEnabled(True)
       +        self.show_change = 0
       +        self.show_used = 0
       +        self.change_button = QComboBox(self)
       +        self.change_button.currentIndexChanged.connect(self.toggle_change)
       +        for t in [_('All'), _('Receiving'), _('Change')]:
       +            self.change_button.addItem(t)
       +        self.used_button = QComboBox(self)
       +        self.used_button.currentIndexChanged.connect(self.toggle_used)
       +        for t in [_('All'), _('Unused'), _('Funded'), _('Used')]:
       +            self.used_button.addItem(t)
       +
       +    def get_toolbar_buttons(self):
       +        return QLabel(_("Filter:")), self.change_button, self.used_button
       +
       +    def on_hide_toolbar(self):
       +        self.show_change = 0
       +        self.show_used = 0
       +        self.update()
       +
       +    def save_toolbar_state(self, state, config):
       +        config.set_key('show_toolbar_addresses', state)
       +
       +    def refresh_headers(self):
       +        headers = [_('Type'), _('Address'), _('Label'), _('Balance')]
       +        fx = self.parent.fx
       +        if fx and fx.get_fiat_address_config():
       +            headers.extend([_(fx.get_currency()+' Balance')])
       +        headers.extend([_('Tx')])
       +        self.update_headers(headers)
       +
       +    def toggle_change(self, state):
       +        if state == self.show_change:
       +            return
       +        self.show_change = state
       +        self.update()
       +
       +    def toggle_used(self, state):
       +        if state == self.show_used:
       +            return
       +        self.show_used = state
       +        self.update()
       +
       +    def on_update(self):
       +        self.wallet = self.parent.wallet
       +        item = self.currentItem()
       +        current_address = item.data(0, Qt.UserRole) if item else None
       +        if self.show_change == 1:
       +            addr_list = self.wallet.get_receiving_addresses()
       +        elif self.show_change == 2:
       +            addr_list = self.wallet.get_change_addresses()
       +        else:
       +            addr_list = self.wallet.get_addresses()
       +        self.clear()
       +        for address in addr_list:
       +            num = len(self.wallet.get_address_history(address))
       +            is_used = self.wallet.is_used(address)
       +            label = self.wallet.labels.get(address, '')
       +            c, u, x = self.wallet.get_addr_balance(address)
       +            balance = c + u + x
       +            if self.show_used == 1 and (balance or is_used):
       +                continue
       +            if self.show_used == 2 and balance == 0:
       +                continue
       +            if self.show_used == 3 and not is_used:
       +                continue
       +            balance_text = self.parent.format_amount(balance, whitespaces=True)
       +            fx = self.parent.fx
       +            # create item
       +            if fx and fx.get_fiat_address_config():
       +                rate = fx.exchange_rate()
       +                fiat_balance = fx.value_str(balance, rate)
       +                address_item = SortableTreeWidgetItem(['', address, label, balance_text, fiat_balance, "%d"%num])
       +            else:
       +                address_item = SortableTreeWidgetItem(['', address, label, balance_text, "%d"%num])
       +            # align text and set fonts
       +            for i in range(address_item.columnCount()):
       +                address_item.setTextAlignment(i, Qt.AlignVCenter)
       +                if i not in (0, 2):
       +                    address_item.setFont(i, QFont(MONOSPACE_FONT))
       +            if fx and fx.get_fiat_address_config():
       +                address_item.setTextAlignment(4, Qt.AlignRight | Qt.AlignVCenter)
       +            # setup column 0
       +            if self.wallet.is_change(address):
       +                address_item.setText(0, _('change'))
       +                address_item.setBackground(0, ColorScheme.YELLOW.as_color(True))
       +            else:
       +                address_item.setText(0, _('receiving'))
       +                address_item.setBackground(0, ColorScheme.GREEN.as_color(True))
       +            address_item.setData(0, Qt.UserRole, address)  # column 0; independent from address column
       +            # setup column 1
       +            if self.wallet.is_frozen(address):
       +                address_item.setBackground(1, ColorScheme.BLUE.as_color(True))
       +            if self.wallet.is_beyond_limit(address):
       +                address_item.setBackground(1, ColorScheme.RED.as_color(True))
       +            # add item
       +            self.addChild(address_item)
       +            if address == current_address:
       +                self.setCurrentItem(address_item)
       +
       +    def create_menu(self, position):
       +        from electrum.wallet import Multisig_Wallet
       +        is_multisig = isinstance(self.wallet, Multisig_Wallet)
       +        can_delete = self.wallet.can_delete_address()
       +        selected = self.selectedItems()
       +        multi_select = len(selected) > 1
       +        addrs = [item.text(1) for item in selected]
       +        if not addrs:
       +            return
       +        if not multi_select:
       +            item = self.itemAt(position)
       +            col = self.currentColumn()
       +            if not item:
       +                return
       +            addr = addrs[0]
       +            if not is_address(addr):
       +                item.setExpanded(not item.isExpanded())
       +                return
       +
       +        menu = QMenu()
       +        if not multi_select:
       +            column_title = self.headerItem().text(col)
       +            copy_text = item.text(col)
       +            menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(copy_text))
       +            menu.addAction(_('Details'), lambda: self.parent.show_address(addr))
       +            if col in self.editable_columns:
       +                menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, col))
       +            menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr))
       +            if self.wallet.can_export():
       +                menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr))
       +            if not is_multisig and not self.wallet.is_watching_only():
       +                menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
       +                menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
       +            if can_delete:
       +                menu.addAction(_("Remove from wallet"), lambda: self.parent.remove_address(addr))
       +            addr_URL = block_explorer_URL(self.config, 'addr', addr)
       +            if addr_URL:
       +                menu.addAction(_("View on block explorer"), lambda: webbrowser.open(addr_URL))
       +
       +            if not self.wallet.is_frozen(addr):
       +                menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state([addr], True))
       +            else:
       +                menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state([addr], False))
       +
       +        coins = self.wallet.get_utxos(addrs)
       +        if coins:
       +            menu.addAction(_("Spend from"), lambda: self.parent.spend_coins(coins))
       +
       +        run_hook('receive_menu', menu, addrs, self.wallet)
       +        menu.exec_(self.viewport().mapToGlobal(position))
       +
       +    def on_permit_edit(self, item, column):
       +        # labels for headings, e.g. "receiving" or "used" should not be editable
       +        return item.childCount() == 0
   DIR diff --git a/gui/qt/amountedit.py b/electrum/gui/qt/amountedit.py
   DIR diff --git a/electrum/gui/qt/completion_text_edit.py b/electrum/gui/qt/completion_text_edit.py
       t@@ -0,0 +1,120 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2018 The Electrum developers
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from PyQt5.QtGui import *
       +from PyQt5.QtCore import *
       +from PyQt5.QtWidgets import *
       +from .util import ButtonsTextEdit
       +
       +class CompletionTextEdit(ButtonsTextEdit):
       +
       +    def __init__(self, parent=None):
       +        super(CompletionTextEdit, self).__init__(parent)
       +        self.completer = None
       +        self.moveCursor(QTextCursor.End)
       +        self.disable_suggestions()
       +
       +    def set_completer(self, completer):
       +        self.completer = completer
       +        self.initialize_completer()
       +
       +    def initialize_completer(self):
       +        self.completer.setWidget(self)
       +        self.completer.setCompletionMode(QCompleter.PopupCompletion)
       +        self.completer.activated.connect(self.insert_completion)
       +        self.enable_suggestions()
       +
       +    def insert_completion(self, completion):
       +        if self.completer.widget() != self:
       +            return
       +        text_cursor = self.textCursor()
       +        extra = len(completion) - len(self.completer.completionPrefix())
       +        text_cursor.movePosition(QTextCursor.Left)
       +        text_cursor.movePosition(QTextCursor.EndOfWord)
       +        if extra == 0:
       +            text_cursor.insertText(" ")
       +        else:
       +            text_cursor.insertText(completion[-extra:] + " ")
       +        self.setTextCursor(text_cursor)
       +
       +    def text_under_cursor(self):
       +        tc = self.textCursor()
       +        tc.select(QTextCursor.WordUnderCursor)
       +        return tc.selectedText()
       +
       +    def enable_suggestions(self):
       +        self.suggestions_enabled = True
       +
       +    def disable_suggestions(self):
       +        self.suggestions_enabled = False
       +
       +    def keyPressEvent(self, e):
       +        if self.isReadOnly():
       +            return
       +
       +        if self.is_special_key(e):
       +            e.ignore()
       +            return
       +
       +        QPlainTextEdit.keyPressEvent(self, e)
       +
       +        ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier)
       +        if self.completer is None or (ctrlOrShift and not e.text()):
       +            return
       +
       +        if not self.suggestions_enabled:
       +            return
       +
       +        eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
       +        hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift
       +        completionPrefix = self.text_under_cursor()
       +
       +        if hasModifier or not e.text() or len(completionPrefix) < 1 or eow.find(e.text()[-1]) >= 0:
       +            self.completer.popup().hide()
       +            return
       +
       +        if completionPrefix != self.completer.completionPrefix():
       +            self.completer.setCompletionPrefix(completionPrefix)
       +            self.completer.popup().setCurrentIndex(self.completer.completionModel().index(0, 0))
       +
       +        cr = self.cursorRect()
       +        cr.setWidth(self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width())
       +        self.completer.complete(cr)
       +
       +    def is_special_key(self, e):
       +        if self.completer != None and self.completer.popup().isVisible():
       +            if e.key() in [Qt.Key_Enter, Qt.Key_Return]:
       +                return True
       +        if e.key() in [Qt.Key_Tab, Qt.Key_Down, Qt.Key_Up]:
       +            return True
       +        return False
       +
       +if __name__ == "__main__":
       +    app = QApplication([])
       +    completer = QCompleter(["alabama", "arkansas", "avocado", "breakfast", "sausage"])
       +    te = CompletionTextEdit()
       +    te.set_completer(completer)
       +    te.show()
       +    app.exec_()
   DIR diff --git a/gui/qt/console.py b/electrum/gui/qt/console.py
   DIR diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py
       t@@ -0,0 +1,98 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2015 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import webbrowser
       +
       +from electrum.i18n import _
       +from electrum.bitcoin import is_address
       +from electrum.util import block_explorer_URL
       +from electrum.plugin import run_hook
       +from PyQt5.QtGui import *
       +from PyQt5.QtCore import *
       +from PyQt5.QtWidgets import (
       +    QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem)
       +from .util import MyTreeWidget, import_meta_gui, export_meta_gui
       +
       +
       +class ContactList(MyTreeWidget):
       +    filter_columns = [0, 1]  # Key, Value
       +
       +    def __init__(self, parent):
       +        MyTreeWidget.__init__(self, parent, self.create_menu, [_('Name'), _('Address')], 0, [0])
       +        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
       +        self.setSortingEnabled(True)
       +
       +    def on_permit_edit(self, item, column):
       +        # openalias items shouldn't be editable
       +        return item.text(1) != "openalias"
       +
       +    def on_edited(self, item, column, prior):
       +        if column == 0:  # Remove old contact if renamed
       +            self.parent.contacts.pop(prior)
       +        self.parent.set_contact(item.text(0), item.text(1))
       +
       +    def import_contacts(self):
       +        import_meta_gui(self.parent, _('contacts'), self.parent.contacts.import_file, self.on_update)
       +
       +    def export_contacts(self):
       +        export_meta_gui(self.parent, _('contacts'), self.parent.contacts.export_file)
       +
       +    def create_menu(self, position):
       +        menu = QMenu()
       +        selected = self.selectedItems()
       +        if not selected:
       +            menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
       +            menu.addAction(_("Import file"), lambda: self.import_contacts())
       +            menu.addAction(_("Export file"), lambda: self.export_contacts())
       +        else:
       +            names = [item.text(0) for item in selected]
       +            keys = [item.text(1) for item in selected]
       +            column = self.currentColumn()
       +            column_title = self.headerItem().text(column)
       +            column_data = '\n'.join([item.text(column) for item in selected])
       +            menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
       +            if column in self.editable_columns:
       +                item = self.currentItem()
       +                menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, column))
       +            menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(keys))
       +            menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(keys))
       +            URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, keys)]
       +            if URLs:
       +                menu.addAction(_("View on block explorer"), lambda: map(webbrowser.open, URLs))
       +
       +        run_hook('create_contact_menu', menu, selected)
       +        menu.exec_(self.viewport().mapToGlobal(position))
       +
       +    def on_update(self):
       +        item = self.currentItem()
       +        current_key = item.data(0, Qt.UserRole) if item else None
       +        self.clear()
       +        for key in sorted(self.parent.contacts.keys()):
       +            _type, name = self.parent.contacts[key]
       +            item = QTreeWidgetItem([name, key])
       +            item.setData(0, Qt.UserRole, key)
       +            self.addTopLevelItem(item)
       +            if key == current_key:
       +                self.setCurrentItem(item)
       +        run_hook('update_contacts_tab', self)
   DIR diff --git a/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py
   DIR diff --git a/gui/qt/fee_slider.py b/electrum/gui/qt/fee_slider.py
   DIR diff --git a/gui/qt/history_list.py b/electrum/gui/qt/history_list.py
   DIR diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py
       t@@ -0,0 +1,644 @@
       +
       +import os
       +import sys
       +import threading
       +import traceback
       +
       +from PyQt5.QtCore import *
       +from PyQt5.QtGui import *
       +from PyQt5.QtWidgets import *
       +
       +from electrum.wallet import Wallet
       +from electrum.storage import WalletStorage
       +from electrum.util import UserCancelled, InvalidPassword
       +from electrum.base_wizard import BaseWizard, HWD_SETUP_DECRYPT_WALLET, GoBack
       +from electrum.i18n import _
       +
       +from .seed_dialog import SeedLayout, KeysLayout
       +from .network_dialog import NetworkChoiceLayout
       +from .util import *
       +from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW
       +
       +
       +MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
       +                     + _("Leave this field empty if you want to disable encryption.")
       +MSG_HW_STORAGE_ENCRYPTION = _("Set wallet file encryption.") + '\n'\
       +                          + _("Your wallet file does not contain secrets, mostly just metadata. ") \
       +                          + _("It also contains your master public key that allows watching your addresses.") + '\n\n'\
       +                          + _("Note: If you enable this setting, you will need your hardware device to open your wallet.")
       +WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' +
       +                 _('A few examples') + ':\n' +
       +                 'p2pkh:KxZcY47uGp9a...       \t-> 1DckmggQM...\n' +
       +                 'p2wpkh-p2sh:KxZcY47uGp9a... \t-> 3NhNeZQXF...\n' +
       +                 'p2wpkh:KxZcY47uGp9a...      \t-> bc1q3fjfk...')
       +# note: full key is KxZcY47uGp9aVQAb6VVvuBs8SwHKgkSR2DbZUzjDzXf2N2GPhG9n
       +
       +
       +class CosignWidget(QWidget):
       +    size = 120
       +
       +    def __init__(self, m, n):
       +        QWidget.__init__(self)
       +        self.R = QRect(0, 0, self.size, self.size)
       +        self.setGeometry(self.R)
       +        self.setMinimumHeight(self.size)
       +        self.setMaximumHeight(self.size)
       +        self.m = m
       +        self.n = n
       +
       +    def set_n(self, n):
       +        self.n = n
       +        self.update()
       +
       +    def set_m(self, m):
       +        self.m = m
       +        self.update()
       +
       +    def paintEvent(self, event):
       +        bgcolor = self.palette().color(QPalette.Background)
       +        pen = QPen(bgcolor, 7, Qt.SolidLine)
       +        qp = QPainter()
       +        qp.begin(self)
       +        qp.setPen(pen)
       +        qp.setRenderHint(QPainter.Antialiasing)
       +        qp.setBrush(Qt.gray)
       +        for i in range(self.n):
       +            alpha = int(16* 360 * i/self.n)
       +            alpha2 = int(16* 360 * 1/self.n)
       +            qp.setBrush(Qt.green if i<self.m else Qt.gray)
       +            qp.drawPie(self.R, alpha, alpha2)
       +        qp.end()
       +
       +
       +
       +def wizard_dialog(func):
       +    def func_wrapper(*args, **kwargs):
       +        run_next = kwargs['run_next']
       +        wizard = args[0]
       +        wizard.back_button.setText(_('Back') if wizard.can_go_back() else _('Cancel'))
       +        try:
       +            out = func(*args, **kwargs)
       +        except GoBack:
       +            wizard.go_back() if wizard.can_go_back() else wizard.close()
       +            return
       +        except UserCancelled:
       +            return
       +        #if out is None:
       +        #    out = ()
       +        if type(out) is not tuple:
       +            out = (out,)
       +        run_next(*out)
       +    return func_wrapper
       +
       +
       +
       +# WindowModalDialog must come first as it overrides show_error
       +class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
       +
       +    accept_signal = pyqtSignal()
       +    synchronized_signal = pyqtSignal(str)
       +
       +    def __init__(self, config, app, plugins, storage):
       +        BaseWizard.__init__(self, config, plugins, storage)
       +        QDialog.__init__(self, None)
       +        self.setWindowTitle('Electrum  -  ' + _('Install Wizard'))
       +        self.app = app
       +        self.config = config
       +        # Set for base base class
       +        self.language_for_seed = config.get('language')
       +        self.setMinimumSize(600, 400)
       +        self.accept_signal.connect(self.accept)
       +        self.title = QLabel()
       +        self.main_widget = QWidget()
       +        self.back_button = QPushButton(_("Back"), self)
       +        self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel'))
       +        self.next_button = QPushButton(_("Next"), self)
       +        self.next_button.setDefault(True)
       +        self.logo = QLabel()
       +        self.please_wait = QLabel(_("Please wait..."))
       +        self.please_wait.setAlignment(Qt.AlignCenter)
       +        self.icon_filename = None
       +        self.loop = QEventLoop()
       +        self.rejected.connect(lambda: self.loop.exit(0))
       +        self.back_button.clicked.connect(lambda: self.loop.exit(1))
       +        self.next_button.clicked.connect(lambda: self.loop.exit(2))
       +        outer_vbox = QVBoxLayout(self)
       +        inner_vbox = QVBoxLayout()
       +        inner_vbox.addWidget(self.title)
       +        inner_vbox.addWidget(self.main_widget)
       +        inner_vbox.addStretch(1)
       +        inner_vbox.addWidget(self.please_wait)
       +        inner_vbox.addStretch(1)
       +        scroll_widget = QWidget()
       +        scroll_widget.setLayout(inner_vbox)
       +        scroll = QScrollArea()
       +        scroll.setWidget(scroll_widget)
       +        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
       +        scroll.setWidgetResizable(True)
       +        icon_vbox = QVBoxLayout()
       +        icon_vbox.addWidget(self.logo)
       +        icon_vbox.addStretch(1)
       +        hbox = QHBoxLayout()
       +        hbox.addLayout(icon_vbox)
       +        hbox.addSpacing(5)
       +        hbox.addWidget(scroll)
       +        hbox.setStretchFactor(scroll, 1)
       +        outer_vbox.addLayout(hbox)
       +        outer_vbox.addLayout(Buttons(self.back_button, self.next_button))
       +        self.set_icon(':icons/electrum.png')
       +        self.show()
       +        self.raise_()
       +        self.refresh_gui()  # Need for QT on MacOSX.  Lame.
       +
       +    def run_and_get_wallet(self, get_wallet_from_daemon):
       +
       +        vbox = QVBoxLayout()
       +        hbox = QHBoxLayout()
       +        hbox.addWidget(QLabel(_('Wallet') + ':'))
       +        self.name_e = QLineEdit()
       +        hbox.addWidget(self.name_e)
       +        button = QPushButton(_('Choose...'))
       +        hbox.addWidget(button)
       +        vbox.addLayout(hbox)
       +
       +        self.msg_label = QLabel('')
       +        vbox.addWidget(self.msg_label)
       +        hbox2 = QHBoxLayout()
       +        self.pw_e = QLineEdit('', self)
       +        self.pw_e.setFixedWidth(150)
       +        self.pw_e.setEchoMode(2)
       +        self.pw_label = QLabel(_('Password') + ':')
       +        hbox2.addWidget(self.pw_label)
       +        hbox2.addWidget(self.pw_e)
       +        hbox2.addStretch()
       +        vbox.addLayout(hbox2)
       +        self.set_layout(vbox, title=_('Electrum wallet'))
       +
       +        wallet_folder = os.path.dirname(self.storage.path)
       +
       +        def on_choose():
       +            path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder)
       +            if path:
       +                self.name_e.setText(path)
       +
       +        def on_filename(filename):
       +            path = os.path.join(wallet_folder, filename)
       +            wallet_from_memory = get_wallet_from_daemon(path)
       +            try:
       +                if wallet_from_memory:
       +                    self.storage = wallet_from_memory.storage
       +                else:
       +                    self.storage = WalletStorage(path, manual_upgrades=True)
       +                self.next_button.setEnabled(True)
       +            except BaseException:
       +                traceback.print_exc(file=sys.stderr)
       +                self.storage = None
       +                self.next_button.setEnabled(False)
       +            if self.storage:
       +                if not self.storage.file_exists():
       +                    msg =_("This file does not exist.") + '\n' \
       +                          + _("Press 'Next' to create this wallet, or choose another file.")
       +                    pw = False
       +                elif not wallet_from_memory:
       +                    if self.storage.is_encrypted_with_user_pw():
       +                        msg = _("This file is encrypted with a password.") + '\n' \
       +                              + _('Enter your password or choose another file.')
       +                        pw = True
       +                    elif self.storage.is_encrypted_with_hw_device():
       +                        msg = _("This file is encrypted using a hardware device.") + '\n' \
       +                              + _("Press 'Next' to choose device to decrypt.")
       +                        pw = False
       +                    else:
       +                        msg = _("Press 'Next' to open this wallet.")
       +                        pw = False
       +                else:
       +                    msg = _("This file is already open in memory.") + "\n" \
       +                        + _("Press 'Next' to create/focus window.")
       +                    pw = False
       +            else:
       +                msg = _('Cannot read file')
       +                pw = False
       +            self.msg_label.setText(msg)
       +            if pw:
       +                self.pw_label.show()
       +                self.pw_e.show()
       +                self.pw_e.setFocus()
       +            else:
       +                self.pw_label.hide()
       +                self.pw_e.hide()
       +
       +        button.clicked.connect(on_choose)
       +        self.name_e.textChanged.connect(on_filename)
       +        n = os.path.basename(self.storage.path)
       +        self.name_e.setText(n)
       +
       +        while True:
       +            if self.loop.exec_() != 2:  # 2 = next
       +                return
       +            if self.storage.file_exists() and not self.storage.is_encrypted():
       +                break
       +            if not self.storage.file_exists():
       +                break
       +            wallet_from_memory = get_wallet_from_daemon(self.storage.path)
       +            if wallet_from_memory:
       +                return wallet_from_memory
       +            if self.storage.file_exists() and self.storage.is_encrypted():
       +                if self.storage.is_encrypted_with_user_pw():
       +                    password = self.pw_e.text()
       +                    try:
       +                        self.storage.decrypt(password)
       +                        break
       +                    except InvalidPassword as e:
       +                        QMessageBox.information(None, _('Error'), str(e))
       +                        continue
       +                    except BaseException as e:
       +                        traceback.print_exc(file=sys.stdout)
       +                        QMessageBox.information(None, _('Error'), str(e))
       +                        return
       +                elif self.storage.is_encrypted_with_hw_device():
       +                    try:
       +                        self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET)
       +                    except InvalidPassword as e:
       +                        QMessageBox.information(
       +                            None, _('Error'),
       +                            _('Failed to decrypt using this hardware device.') + '\n' +
       +                            _('If you use a passphrase, make sure it is correct.'))
       +                        self.stack = []
       +                        return self.run_and_get_wallet(get_wallet_from_daemon)
       +                    except BaseException as e:
       +                        traceback.print_exc(file=sys.stdout)
       +                        QMessageBox.information(None, _('Error'), str(e))
       +                        return
       +                    if self.storage.is_past_initial_decryption():
       +                        break
       +                    else:
       +                        return
       +                else:
       +                    raise Exception('Unexpected encryption version')
       +
       +        path = self.storage.path
       +        if self.storage.requires_split():
       +            self.hide()
       +            msg = _("The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n"
       +                    "Do you want to split your wallet into multiple files?").format(path)
       +            if not self.question(msg):
       +                return
       +            file_list = '\n'.join(self.storage.split_accounts())
       +            msg = _('Your accounts have been moved to') + ':\n' + file_list + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path
       +            if self.question(msg):
       +                os.remove(path)
       +                self.show_warning(_('The file was removed'))
       +            return
       +
       +        action = self.storage.get_action()
       +        if action and action not in ('new', 'upgrade_storage'):
       +            self.hide()
       +            msg = _("The file '{}' contains an incompletely created wallet.\n"
       +                    "Do you want to complete its creation now?").format(path)
       +            if not self.question(msg):
       +                if self.question(_("Do you want to delete '{}'?").format(path)):
       +                    os.remove(path)
       +                    self.show_warning(_('The file was removed'))
       +                return
       +            self.show()
       +        if action:
       +            # self.wallet is set in run
       +            self.run(action)
       +            return self.wallet
       +
       +        self.wallet = Wallet(self.storage)
       +        return self.wallet
       +
       +    def finished(self):
       +        """Called in hardware client wrapper, in order to close popups."""
       +        return
       +
       +    def on_error(self, exc_info):
       +        if not isinstance(exc_info[1], UserCancelled):
       +            traceback.print_exception(*exc_info)
       +            self.show_error(str(exc_info[1]))
       +
       +    def set_icon(self, filename):
       +        prior_filename, self.icon_filename = self.icon_filename, filename
       +        self.logo.setPixmap(QPixmap(filename).scaledToWidth(60, mode=Qt.SmoothTransformation))
       +        return prior_filename
       +
       +    def set_layout(self, layout, title=None, next_enabled=True):
       +        self.title.setText("<b>%s</b>"%title if title else "")
       +        self.title.setVisible(bool(title))
       +        # Get rid of any prior layout by assigning it to a temporary widget
       +        prior_layout = self.main_widget.layout()
       +        if prior_layout:
       +            QWidget().setLayout(prior_layout)
       +        self.main_widget.setLayout(layout)
       +        self.back_button.setEnabled(True)
       +        self.next_button.setEnabled(next_enabled)
       +        if next_enabled:
       +            self.next_button.setFocus()
       +        self.main_widget.setVisible(True)
       +        self.please_wait.setVisible(False)
       +
       +    def exec_layout(self, layout, title=None, raise_on_cancel=True,
       +                        next_enabled=True):
       +        self.set_layout(layout, title, next_enabled)
       +        result = self.loop.exec_()
       +        if not result and raise_on_cancel:
       +            raise UserCancelled
       +        if result == 1:
       +            raise GoBack from None
       +        self.title.setVisible(False)
       +        self.back_button.setEnabled(False)
       +        self.next_button.setEnabled(False)
       +        self.main_widget.setVisible(False)
       +        self.please_wait.setVisible(True)
       +        self.refresh_gui()
       +        return result
       +
       +    def refresh_gui(self):
       +        # For some reason, to refresh the GUI this needs to be called twice
       +        self.app.processEvents()
       +        self.app.processEvents()
       +
       +    def remove_from_recently_open(self, filename):
       +        self.config.remove_from_recently_open(filename)
       +
       +    def text_input(self, title, message, is_valid, allow_multi=False):
       +        slayout = KeysLayout(parent=self, header_layout=message, is_valid=is_valid,
       +                             allow_multi=allow_multi)
       +        self.exec_layout(slayout, title, next_enabled=False)
       +        return slayout.get_text()
       +
       +    def seed_input(self, title, message, is_seed, options):
       +        slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self)
       +        self.exec_layout(slayout, title, next_enabled=False)
       +        return slayout.get_seed(), slayout.is_bip39, slayout.is_ext
       +
       +    @wizard_dialog
       +    def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False, show_wif_help=False):
       +        header_layout = QHBoxLayout()
       +        label = WWLabel(message)
       +        label.setMinimumWidth(400)
       +        header_layout.addWidget(label)
       +        if show_wif_help:
       +            header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
       +        return self.text_input(title, header_layout, is_valid, allow_multi)
       +
       +    @wizard_dialog
       +    def add_cosigner_dialog(self, run_next, index, is_valid):
       +        title = _("Add Cosigner") + " %d"%index
       +        message = ' '.join([
       +            _('Please enter the master public key (xpub) of your cosigner.'),
       +            _('Enter their master private key (xprv) if you want to be able to sign for them.')
       +        ])
       +        return self.text_input(title, message, is_valid)
       +
       +    @wizard_dialog
       +    def restore_seed_dialog(self, run_next, test):
       +        options = []
       +        if self.opt_ext:
       +            options.append('ext')
       +        if self.opt_bip39:
       +            options.append('bip39')
       +        title = _('Enter Seed')
       +        message = _('Please enter your seed phrase in order to restore your wallet.')
       +        return self.seed_input(title, message, test, options)
       +
       +    @wizard_dialog
       +    def confirm_seed_dialog(self, run_next, test):
       +        self.app.clipboard().clear()
       +        title = _('Confirm Seed')
       +        message = ' '.join([
       +            _('Your seed is important!'),
       +            _('If you lose your seed, your money will be permanently lost.'),
       +            _('To make sure that you have properly saved your seed, please retype it here.')
       +        ])
       +        seed, is_bip39, is_ext = self.seed_input(title, message, test, None)
       +        return seed
       +
       +    @wizard_dialog
       +    def show_seed_dialog(self, run_next, seed_text):
       +        title =  _("Your wallet generation seed is:")
       +        slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext'])
       +        self.exec_layout(slayout)
       +        return slayout.is_ext
       +
       +    def pw_layout(self, msg, kind, force_disable_encrypt_cb):
       +        playout = PasswordLayout(None, msg, kind, self.next_button,
       +                                 force_disable_encrypt_cb=force_disable_encrypt_cb)
       +        playout.encrypt_cb.setChecked(True)
       +        self.exec_layout(playout.layout())
       +        return playout.new_password(), playout.encrypt_cb.isChecked()
       +
       +    @wizard_dialog
       +    def request_password(self, run_next, force_disable_encrypt_cb=False):
       +        """Request the user enter a new password and confirm it.  Return
       +        the password or None for no password."""
       +        return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb)
       +
       +    @wizard_dialog
       +    def request_storage_encryption(self, run_next):
       +        playout = PasswordLayoutForHW(None, MSG_HW_STORAGE_ENCRYPTION, PW_NEW, self.next_button)
       +        playout.encrypt_cb.setChecked(True)
       +        self.exec_layout(playout.layout())
       +        return playout.encrypt_cb.isChecked()
       +
       +    def show_restore(self, wallet, network):
       +        # FIXME: these messages are shown after the install wizard is
       +        # finished and the window closed.  On macOS they appear parented
       +        # with a re-appeared ghost install wizard window...
       +        if network:
       +            def task():
       +                wallet.wait_until_synchronized()
       +                if wallet.is_found():
       +                    msg = _("Recovery successful")
       +                else:
       +                    msg = _("No transactions found for this seed")
       +                self.synchronized_signal.emit(msg)
       +            self.synchronized_signal.connect(self.show_message)
       +            t = threading.Thread(target = task)
       +            t.daemon = True
       +            t.start()
       +        else:
       +            msg = _("This wallet was restored offline. It may "
       +                    "contain more addresses than displayed.")
       +            self.show_message(msg)
       +
       +    @wizard_dialog
       +    def confirm_dialog(self, title, message, run_next):
       +        self.confirm(message, title)
       +
       +    def confirm(self, message, title):
       +        label = WWLabel(message)
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(label)
       +        self.exec_layout(vbox, title)
       +
       +    @wizard_dialog
       +    def action_dialog(self, action, run_next):
       +        self.run(action)
       +
       +    def terminate(self):
       +        self.accept_signal.emit()
       +
       +    def waiting_dialog(self, task, msg, on_finished=None):
       +        label = WWLabel(msg)
       +        vbox = QVBoxLayout()
       +        vbox.addSpacing(100)
       +        label.setMinimumWidth(300)
       +        label.setAlignment(Qt.AlignCenter)
       +        vbox.addWidget(label)
       +        self.set_layout(vbox, next_enabled=False)
       +        self.back_button.setEnabled(False)
       +
       +        t = threading.Thread(target=task)
       +        t.start()
       +        while True:
       +            t.join(1.0/60)
       +            if t.is_alive():
       +                self.refresh_gui()
       +            else:
       +                break
       +        if on_finished:
       +            on_finished()
       +
       +    @wizard_dialog
       +    def choice_dialog(self, title, message, choices, run_next):
       +        c_values = [x[0] for x in choices]
       +        c_titles = [x[1] for x in choices]
       +        clayout = ChoicesLayout(message, c_titles)
       +        vbox = QVBoxLayout()
       +        vbox.addLayout(clayout.layout())
       +        self.exec_layout(vbox, title)
       +        action = c_values[clayout.selected_index()]
       +        return action
       +
       +    def query_choice(self, msg, choices):
       +        """called by hardware wallets"""
       +        clayout = ChoicesLayout(msg, choices)
       +        vbox = QVBoxLayout()
       +        vbox.addLayout(clayout.layout())
       +        self.exec_layout(vbox, '')
       +        return clayout.selected_index()
       +
       +    @wizard_dialog
       +    def choice_and_line_dialog(self, title, message1, choices, message2,
       +                               test_text, run_next) -> (str, str):
       +        vbox = QVBoxLayout()
       +
       +        c_values = [x[0] for x in choices]
       +        c_titles = [x[1] for x in choices]
       +        c_default_text = [x[2] for x in choices]
       +        def on_choice_click(clayout):
       +            idx = clayout.selected_index()
       +            line.setText(c_default_text[idx])
       +        clayout = ChoicesLayout(message1, c_titles, on_choice_click)
       +        vbox.addLayout(clayout.layout())
       +
       +        vbox.addSpacing(50)
       +        vbox.addWidget(WWLabel(message2))
       +
       +        line = QLineEdit()
       +        def on_text_change(text):
       +            self.next_button.setEnabled(test_text(text))
       +        line.textEdited.connect(on_text_change)
       +        on_choice_click(clayout)  # set default text for "line"
       +        vbox.addWidget(line)
       +
       +        self.exec_layout(vbox, title)
       +        choice = c_values[clayout.selected_index()]
       +        return str(line.text()), choice
       +
       +    @wizard_dialog
       +    def line_dialog(self, run_next, title, message, default, test, warning='',
       +                    presets=()):
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(WWLabel(message))
       +        line = QLineEdit()
       +        line.setText(default)
       +        def f(text):
       +            self.next_button.setEnabled(test(text))
       +        line.textEdited.connect(f)
       +        vbox.addWidget(line)
       +        vbox.addWidget(WWLabel(warning))
       +
       +        for preset in presets:
       +            button = QPushButton(preset[0])
       +            button.clicked.connect(lambda __, text=preset[1]: line.setText(text))
       +            button.setMinimumWidth(150)
       +            hbox = QHBoxLayout()
       +            hbox.addWidget(button, alignment=Qt.AlignCenter)
       +            vbox.addLayout(hbox)
       +
       +        self.exec_layout(vbox, title, next_enabled=test(default))
       +        return ' '.join(line.text().split())
       +
       +    @wizard_dialog
       +    def show_xpub_dialog(self, xpub, run_next):
       +        msg = ' '.join([
       +            _("Here is your master public key."),
       +            _("Please share it with your cosigners.")
       +        ])
       +        vbox = QVBoxLayout()
       +        layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False)
       +        vbox.addLayout(layout.layout())
       +        self.exec_layout(vbox, _('Master Public Key'))
       +        return None
       +
       +    def init_network(self, network):
       +        message = _("Electrum communicates with remote servers to get "
       +                  "information about your transactions and addresses. The "
       +                  "servers all fulfill the same purpose only differing in "
       +                  "hardware. In most cases you simply want to let Electrum "
       +                  "pick one at random.  However if you prefer feel free to "
       +                  "select a server manually.")
       +        choices = [_("Auto connect"), _("Select server manually")]
       +        title = _("How do you want to connect to a server? ")
       +        clayout = ChoicesLayout(message, choices)
       +        self.back_button.setText(_('Cancel'))
       +        self.exec_layout(clayout.layout(), title)
       +        r = clayout.selected_index()
       +        if r == 1:
       +            nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
       +            if self.exec_layout(nlayout.layout()):
       +                nlayout.accept()
       +        else:
       +            network.auto_connect = True
       +            self.config.set_key('auto_connect', True, True)
       +
       +    @wizard_dialog
       +    def multisig_dialog(self, run_next):
       +        cw = CosignWidget(2, 2)
       +        m_edit = QSlider(Qt.Horizontal, self)
       +        n_edit = QSlider(Qt.Horizontal, self)
       +        n_edit.setMinimum(2)
       +        n_edit.setMaximum(15)
       +        m_edit.setMinimum(1)
       +        m_edit.setMaximum(2)
       +        n_edit.setValue(2)
       +        m_edit.setValue(2)
       +        n_label = QLabel()
       +        m_label = QLabel()
       +        grid = QGridLayout()
       +        grid.addWidget(n_label, 0, 0)
       +        grid.addWidget(n_edit, 0, 1)
       +        grid.addWidget(m_label, 1, 0)
       +        grid.addWidget(m_edit, 1, 1)
       +        def on_m(m):
       +            m_label.setText(_('Require {0} signatures').format(m))
       +            cw.set_m(m)
       +        def on_n(n):
       +            n_label.setText(_('From {0} cosigners').format(n))
       +            cw.set_n(n)
       +            m_edit.setMaximum(n)
       +        n_edit.valueChanged.connect(on_n)
       +        m_edit.valueChanged.connect(on_m)
       +        on_n(2)
       +        on_m(2)
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(cw)
       +        vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:")))
       +        vbox.addLayout(grid)
       +        self.exec_layout(vbox, _("Multi-Signature Wallet"))
       +        m = int(m_edit.value())
       +        n = int(n_edit.value())
       +        return (m, n)
   DIR diff --git a/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -0,0 +1,3220 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2012 thomasv@gitorious
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import sys, time, threading
       +import os, json, traceback
       +import shutil
       +import weakref
       +import webbrowser
       +import csv
       +from decimal import Decimal
       +import base64
       +from functools import partial
       +
       +from PyQt5.QtGui import *
       +from PyQt5.QtCore import *
       +import PyQt5.QtCore as QtCore
       +
       +from .exception_window import Exception_Hook
       +from PyQt5.QtWidgets import *
       +
       +from electrum import (keystore, simple_config, ecc, constants, util, bitcoin, commands,
       +                      coinchooser, paymentrequest)
       +from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS
       +from electrum.plugin import run_hook
       +from electrum.i18n import _
       +from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
       +                           format_satoshis_plain, NotEnoughFunds, PrintError,
       +                           UserCancelled, NoDynamicFeeEstimates, profiler,
       +                           export_meta, import_meta, bh2u, bfh, InvalidPassword,
       +                           base_units, base_units_list, base_unit_name_to_decimal_point,
       +                           decimal_point_to_base_unit_name, quantize_feerate)
       +from electrum.transaction import Transaction
       +from electrum.wallet import Multisig_Wallet, AddTransactionException, CannotBumpFee
       +
       +from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
       +from .qrcodewidget import QRCodeWidget, QRDialog
       +from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
       +from .transaction_dialog import show_transaction
       +from .fee_slider import FeeSlider
       +from .util import *
       +from .installwizard import WIF_HELP_TEXT
       +
       +
       +class StatusBarButton(QPushButton):
       +    def __init__(self, icon, tooltip, func):
       +        QPushButton.__init__(self, icon, '')
       +        self.setToolTip(tooltip)
       +        self.setFlat(True)
       +        self.setMaximumWidth(25)
       +        self.clicked.connect(self.onPress)
       +        self.func = func
       +        self.setIconSize(QSize(25,25))
       +
       +    def onPress(self, checked=False):
       +        '''Drops the unwanted PyQt5 "checked" argument'''
       +        self.func()
       +
       +    def keyPressEvent(self, e):
       +        if e.key() == Qt.Key_Return:
       +            self.func()
       +
       +
       +from electrum.paymentrequest import PR_PAID
       +
       +
       +class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
       +
       +    payment_request_ok_signal = pyqtSignal()
       +    payment_request_error_signal = pyqtSignal()
       +    notify_transactions_signal = pyqtSignal()
       +    new_fx_quotes_signal = pyqtSignal()
       +    new_fx_history_signal = pyqtSignal()
       +    network_signal = pyqtSignal(str, object)
       +    alias_received_signal = pyqtSignal()
       +    computing_privkeys_signal = pyqtSignal()
       +    show_privkeys_signal = pyqtSignal()
       +
       +    def __init__(self, gui_object, wallet):
       +        QMainWindow.__init__(self)
       +
       +        self.gui_object = gui_object
       +        self.config = config = gui_object.config
       +
       +        self.setup_exception_hook()
       +
       +        self.network = gui_object.daemon.network
       +        self.fx = gui_object.daemon.fx
       +        self.invoices = wallet.invoices
       +        self.contacts = wallet.contacts
       +        self.tray = gui_object.tray
       +        self.app = gui_object.app
       +        self.cleaned_up = False
       +        self.is_max = False
       +        self.payment_request = None
       +        self.checking_accounts = False
       +        self.qr_window = None
       +        self.not_enough_funds = False
       +        self.pluginsdialog = None
       +        self.require_fee_update = False
       +        self.tx_notifications = []
       +        self.tl_windows = []
       +        self.tx_external_keypairs = {}
       +
       +        self.create_status_bar()
       +        self.need_update = threading.Event()
       +
       +        self.decimal_point = config.get('decimal_point', 5)
       +        self.num_zeros     = int(config.get('num_zeros',0))
       +
       +        self.completions = QStringListModel()
       +
       +        self.tabs = tabs = QTabWidget(self)
       +        self.send_tab = self.create_send_tab()
       +        self.receive_tab = self.create_receive_tab()
       +        self.addresses_tab = self.create_addresses_tab()
       +        self.utxo_tab = self.create_utxo_tab()
       +        self.console_tab = self.create_console_tab()
       +        self.contacts_tab = self.create_contacts_tab()
       +        tabs.addTab(self.create_history_tab(), QIcon(":icons/tab_history.png"), _('History'))
       +        tabs.addTab(self.send_tab, QIcon(":icons/tab_send.png"), _('Send'))
       +        tabs.addTab(self.receive_tab, QIcon(":icons/tab_receive.png"), _('Receive'))
       +
       +        def add_optional_tab(tabs, tab, icon, description, name):
       +            tab.tab_icon = icon
       +            tab.tab_description = description
       +            tab.tab_pos = len(tabs)
       +            tab.tab_name = name
       +            if self.config.get('show_{}_tab'.format(name), False):
       +                tabs.addTab(tab, icon, description.replace("&", ""))
       +
       +        add_optional_tab(tabs, self.addresses_tab, QIcon(":icons/tab_addresses.png"), _("&Addresses"), "addresses")
       +        add_optional_tab(tabs, self.utxo_tab, QIcon(":icons/tab_coins.png"), _("Co&ins"), "utxo")
       +        add_optional_tab(tabs, self.contacts_tab, QIcon(":icons/tab_contacts.png"), _("Con&tacts"), "contacts")
       +        add_optional_tab(tabs, self.console_tab, QIcon(":icons/tab_console.png"), _("Con&sole"), "console")
       +
       +        tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
       +        self.setCentralWidget(tabs)
       +
       +        if self.config.get("is_maximized"):
       +            self.showMaximized()
       +
       +        self.setWindowIcon(QIcon(":icons/electrum.png"))
       +        self.init_menubar()
       +
       +        wrtabs = weakref.proxy(tabs)
       +        QShortcut(QKeySequence("Ctrl+W"), self, self.close)
       +        QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
       +        QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
       +        QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: wrtabs.setCurrentIndex((wrtabs.currentIndex() - 1)%wrtabs.count()))
       +        QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: wrtabs.setCurrentIndex((wrtabs.currentIndex() + 1)%wrtabs.count()))
       +
       +        for i in range(wrtabs.count()):
       +            QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: wrtabs.setCurrentIndex(i))
       +
       +        self.payment_request_ok_signal.connect(self.payment_request_ok)
       +        self.payment_request_error_signal.connect(self.payment_request_error)
       +        self.notify_transactions_signal.connect(self.notify_transactions)
       +        self.history_list.setFocus(True)
       +
       +        # network callbacks
       +        if self.network:
       +            self.network_signal.connect(self.on_network_qt)
       +            interests = ['updated', 'new_transaction', 'status',
       +                         'banner', 'verified', 'fee']
       +            # To avoid leaking references to "self" that prevent the
       +            # window from being GC-ed when closed, callbacks should be
       +            # methods of this class only, and specifically not be
       +            # partials, lambdas or methods of subobjects.  Hence...
       +            self.network.register_callback(self.on_network, interests)
       +            # set initial message
       +            self.console.showMessage(self.network.banner)
       +            self.network.register_callback(self.on_quotes, ['on_quotes'])
       +            self.network.register_callback(self.on_history, ['on_history'])
       +            self.new_fx_quotes_signal.connect(self.on_fx_quotes)
       +            self.new_fx_history_signal.connect(self.on_fx_history)
       +
       +        # update fee slider in case we missed the callback
       +        self.fee_slider.update()
       +        self.load_wallet(wallet)
       +        self.connect_slots(gui_object.timer)
       +        self.fetch_alias()
       +
       +    def on_history(self, b):
       +        self.new_fx_history_signal.emit()
       +
       +    def setup_exception_hook(self):
       +        Exception_Hook(self)
       +
       +    def on_fx_history(self):
       +        self.history_list.refresh_headers()
       +        self.history_list.update()
       +        self.address_list.update()
       +
       +    def on_quotes(self, b):
       +        self.new_fx_quotes_signal.emit()
       +
       +    def on_fx_quotes(self):
       +        self.update_status()
       +        # Refresh edits with the new rate
       +        edit = self.fiat_send_e if self.fiat_send_e.is_last_edited else self.amount_e
       +        edit.textEdited.emit(edit.text())
       +        edit = self.fiat_receive_e if self.fiat_receive_e.is_last_edited else self.receive_amount_e
       +        edit.textEdited.emit(edit.text())
       +        # History tab needs updating if it used spot
       +        if self.fx.history_used_spot:
       +            self.history_list.update()
       +
       +    def toggle_tab(self, tab):
       +        show = not self.config.get('show_{}_tab'.format(tab.tab_name), False)
       +        self.config.set_key('show_{}_tab'.format(tab.tab_name), show)
       +        item_text = (_("Hide") if show else _("Show")) + " " + tab.tab_description
       +        tab.menu_action.setText(item_text)
       +        if show:
       +            # Find out where to place the tab
       +            index = len(self.tabs)
       +            for i in range(len(self.tabs)):
       +                try:
       +                    if tab.tab_pos < self.tabs.widget(i).tab_pos:
       +                        index = i
       +                        break
       +                except AttributeError:
       +                    pass
       +            self.tabs.insertTab(index, tab, tab.tab_icon, tab.tab_description.replace("&", ""))
       +        else:
       +            i = self.tabs.indexOf(tab)
       +            self.tabs.removeTab(i)
       +
       +    def push_top_level_window(self, window):
       +        '''Used for e.g. tx dialog box to ensure new dialogs are appropriately
       +        parented.  This used to be done by explicitly providing the parent
       +        window, but that isn't something hardware wallet prompts know.'''
       +        self.tl_windows.append(window)
       +
       +    def pop_top_level_window(self, window):
       +        self.tl_windows.remove(window)
       +
       +    def top_level_window(self, test_func=None):
       +        '''Do the right thing in the presence of tx dialog windows'''
       +        override = self.tl_windows[-1] if self.tl_windows else None
       +        if override and test_func and not test_func(override):
       +            override = None  # only override if ok for test_func
       +        return self.top_level_window_recurse(override, test_func)
       +
       +    def diagnostic_name(self):
       +        return "%s/%s" % (PrintError.diagnostic_name(self),
       +                          self.wallet.basename() if self.wallet else "None")
       +
       +    def is_hidden(self):
       +        return self.isMinimized() or self.isHidden()
       +
       +    def show_or_hide(self):
       +        if self.is_hidden():
       +            self.bring_to_top()
       +        else:
       +            self.hide()
       +
       +    def bring_to_top(self):
       +        self.show()
       +        self.raise_()
       +
       +    def on_error(self, exc_info):
       +        if not isinstance(exc_info[1], UserCancelled):
       +            try:
       +                traceback.print_exception(*exc_info)
       +            except OSError:
       +                pass  # see #4418; try to at least show popup:
       +            self.show_error(str(exc_info[1]))
       +
       +    def on_network(self, event, *args):
       +        if event == 'updated':
       +            self.need_update.set()
       +            self.gui_object.network_updated_signal_obj.network_updated_signal \
       +                .emit(event, args)
       +        elif event == 'new_transaction':
       +            self.tx_notifications.append(args[0])
       +            self.notify_transactions_signal.emit()
       +        elif event in ['status', 'banner', 'verified', 'fee']:
       +            # Handle in GUI thread
       +            self.network_signal.emit(event, args)
       +        else:
       +            self.print_error("unexpected network message:", event, args)
       +
       +    def on_network_qt(self, event, args=None):
       +        # Handle a network message in the GUI thread
       +        if event == 'status':
       +            self.update_status()
       +        elif event == 'banner':
       +            self.console.showMessage(args[0])
       +        elif event == 'verified':
       +            self.history_list.update_item(*args)
       +        elif event == 'fee':
       +            if self.config.is_dynfee():
       +                self.fee_slider.update()
       +                self.do_update_fee()
       +        elif event == 'fee_histogram':
       +            if self.config.is_dynfee():
       +                self.fee_slider.update()
       +                self.do_update_fee()
       +            # todo: update only unconfirmed tx
       +            self.history_list.update()
       +        else:
       +            self.print_error("unexpected network_qt signal:", event, args)
       +
       +    def fetch_alias(self):
       +        self.alias_info = None
       +        alias = self.config.get('alias')
       +        if alias:
       +            alias = str(alias)
       +            def f():
       +                self.alias_info = self.contacts.resolve_openalias(alias)
       +                self.alias_received_signal.emit()
       +            t = threading.Thread(target=f)
       +            t.setDaemon(True)
       +            t.start()
       +
       +    def close_wallet(self):
       +        if self.wallet:
       +            self.print_error('close_wallet', self.wallet.storage.path)
       +        run_hook('close_wallet', self.wallet)
       +
       +    @profiler
       +    def load_wallet(self, wallet):
       +        wallet.thread = TaskThread(self, self.on_error)
       +        self.wallet = wallet
       +        self.update_recently_visited(wallet.storage.path)
       +        # address used to create a dummy transaction and estimate transaction fee
       +        self.history_list.update()
       +        self.address_list.update()
       +        self.utxo_list.update()
       +        self.need_update.set()
       +        # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
       +        self.notify_transactions()
       +        # update menus
       +        self.seed_menu.setEnabled(self.wallet.has_seed())
       +        self.update_lock_icon()
       +        self.update_buttons_on_seed()
       +        self.update_console()
       +        self.clear_receive_tab()
       +        self.request_list.update()
       +        self.tabs.show()
       +        self.init_geometry()
       +        if self.config.get('hide_gui') and self.gui_object.tray.isVisible():
       +            self.hide()
       +        else:
       +            self.show()
       +        self.watching_only_changed()
       +        run_hook('load_wallet', wallet, self)
       +
       +    def init_geometry(self):
       +        winpos = self.wallet.storage.get("winpos-qt")
       +        try:
       +            screen = self.app.desktop().screenGeometry()
       +            assert screen.contains(QRect(*winpos))
       +            self.setGeometry(*winpos)
       +        except:
       +            self.print_error("using default geometry")
       +            self.setGeometry(100, 100, 840, 400)
       +
       +    def watching_only_changed(self):
       +        name = "Electrum Testnet" if constants.net.TESTNET else "Electrum"
       +        title = '%s %s  -  %s' % (name, self.wallet.electrum_version,
       +                                        self.wallet.basename())
       +        extra = [self.wallet.storage.get('wallet_type', '?')]
       +        if self.wallet.is_watching_only():
       +            self.warn_if_watching_only()
       +            extra.append(_('watching only'))
       +        title += '  [%s]'% ', '.join(extra)
       +        self.setWindowTitle(title)
       +        self.password_menu.setEnabled(self.wallet.may_have_password())
       +        self.import_privkey_menu.setVisible(self.wallet.can_import_privkey())
       +        self.import_address_menu.setVisible(self.wallet.can_import_address())
       +        self.export_menu.setEnabled(self.wallet.can_export())
       +
       +    def warn_if_watching_only(self):
       +        if self.wallet.is_watching_only():
       +            msg = ' '.join([
       +                _("This wallet is watching-only."),
       +                _("This means you will not be able to spend Bitcoins with it."),
       +                _("Make sure you own the seed phrase or the private keys, before you request Bitcoins to be sent to this wallet.")
       +            ])
       +            self.show_warning(msg, title=_('Information'))
       +
       +    def open_wallet(self):
       +        try:
       +            wallet_folder = self.get_wallet_folder()
       +        except FileNotFoundError as e:
       +            self.show_error(str(e))
       +            return
       +        filename, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder)
       +        if not filename:
       +            return
       +        self.gui_object.new_window(filename)
       +
       +
       +    def backup_wallet(self):
       +        path = self.wallet.storage.path
       +        wallet_folder = os.path.dirname(path)
       +        filename, __ = QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder)
       +        if not filename:
       +            return
       +        new_path = os.path.join(wallet_folder, filename)
       +        if new_path != path:
       +            try:
       +                shutil.copy2(path, new_path)
       +                self.show_message(_("A copy of your wallet file was created in")+" '%s'" % str(new_path), title=_("Wallet backup created"))
       +            except BaseException as reason:
       +                self.show_critical(_("Electrum was unable to copy your wallet file to the specified location.") + "\n" + str(reason), title=_("Unable to create backup"))
       +
       +    def update_recently_visited(self, filename):
       +        recent = self.config.get('recently_open', [])
       +        try:
       +            sorted(recent)
       +        except:
       +            recent = []
       +        if filename in recent:
       +            recent.remove(filename)
       +        recent.insert(0, filename)
       +        recent = recent[:5]
       +        self.config.set_key('recently_open', recent)
       +        self.recently_visited_menu.clear()
       +        for i, k in enumerate(sorted(recent)):
       +            b = os.path.basename(k)
       +            def loader(k):
       +                return lambda: self.gui_object.new_window(k)
       +            self.recently_visited_menu.addAction(b, loader(k)).setShortcut(QKeySequence("Ctrl+%d"%(i+1)))
       +        self.recently_visited_menu.setEnabled(len(recent))
       +
       +    def get_wallet_folder(self):
       +        return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))
       +
       +    def new_wallet(self):
       +        try:
       +            wallet_folder = self.get_wallet_folder()
       +        except FileNotFoundError as e:
       +            self.show_error(str(e))
       +            return
       +        i = 1
       +        while True:
       +            filename = "wallet_%d" % i
       +            if filename in os.listdir(wallet_folder):
       +                i += 1
       +            else:
       +                break
       +        full_path = os.path.join(wallet_folder, filename)
       +        self.gui_object.start_new_window(full_path, None)
       +
       +    def init_menubar(self):
       +        menubar = QMenuBar()
       +
       +        file_menu = menubar.addMenu(_("&File"))
       +        self.recently_visited_menu = file_menu.addMenu(_("&Recently open"))
       +        file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
       +        file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
       +        file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
       +        file_menu.addAction(_("Delete"), self.remove_wallet)
       +        file_menu.addSeparator()
       +        file_menu.addAction(_("&Quit"), self.close)
       +
       +        wallet_menu = menubar.addMenu(_("&Wallet"))
       +        wallet_menu.addAction(_("&Information"), self.show_master_public_keys)
       +        wallet_menu.addSeparator()
       +        self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
       +        self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
       +        self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
       +        self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
       +        self.import_privkey_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
       +        self.export_menu = self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
       +        self.import_address_menu = wallet_menu.addAction(_("Import addresses"), self.import_addresses)
       +        wallet_menu.addSeparator()
       +
       +        addresses_menu = wallet_menu.addMenu(_("&Addresses"))
       +        addresses_menu.addAction(_("&Filter"), lambda: self.address_list.toggle_toolbar(self.config))
       +        labels_menu = wallet_menu.addMenu(_("&Labels"))
       +        labels_menu.addAction(_("&Import"), self.do_import_labels)
       +        labels_menu.addAction(_("&Export"), self.do_export_labels)
       +        history_menu = wallet_menu.addMenu(_("&History"))
       +        history_menu.addAction(_("&Filter"), lambda: self.history_list.toggle_toolbar(self.config))
       +        history_menu.addAction(_("&Summary"), self.history_list.show_summary)
       +        history_menu.addAction(_("&Plot"), self.history_list.plot_history_dialog)
       +        history_menu.addAction(_("&Export"), self.history_list.export_history_dialog)
       +        contacts_menu = wallet_menu.addMenu(_("Contacts"))
       +        contacts_menu.addAction(_("&New"), self.new_contact_dialog)
       +        contacts_menu.addAction(_("Import"), lambda: self.contact_list.import_contacts())
       +        contacts_menu.addAction(_("Export"), lambda: self.contact_list.export_contacts())
       +        invoices_menu = wallet_menu.addMenu(_("Invoices"))
       +        invoices_menu.addAction(_("Import"), lambda: self.invoice_list.import_invoices())
       +        invoices_menu.addAction(_("Export"), lambda: self.invoice_list.export_invoices())
       +
       +        wallet_menu.addSeparator()
       +        wallet_menu.addAction(_("Find"), self.toggle_search).setShortcut(QKeySequence("Ctrl+F"))
       +
       +        def add_toggle_action(view_menu, tab):
       +            is_shown = self.config.get('show_{}_tab'.format(tab.tab_name), False)
       +            item_name = (_("Hide") if is_shown else _("Show")) + " " + tab.tab_description
       +            tab.menu_action = view_menu.addAction(item_name, lambda: self.toggle_tab(tab))
       +
       +        view_menu = menubar.addMenu(_("&View"))
       +        add_toggle_action(view_menu, self.addresses_tab)
       +        add_toggle_action(view_menu, self.utxo_tab)
       +        add_toggle_action(view_menu, self.contacts_tab)
       +        add_toggle_action(view_menu, self.console_tab)
       +
       +        tools_menu = menubar.addMenu(_("&Tools"))
       +
       +        # Settings / Preferences are all reserved keywords in macOS using this as work around
       +        tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
       +        tools_menu.addAction(_("&Network"), lambda: self.gui_object.show_network_dialog(self))
       +        tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
       +        tools_menu.addSeparator()
       +        tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
       +        tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
       +        tools_menu.addSeparator()
       +
       +        paytomany_menu = tools_menu.addAction(_("&Pay to many"), self.paytomany)
       +
       +        raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
       +        raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
       +        raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
       +        raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
       +        raw_transaction_menu.addAction(_("&From QR code"), self.read_tx_from_qrcode)
       +        self.raw_transaction_menu = raw_transaction_menu
       +        run_hook('init_menubar_tools', self, tools_menu)
       +
       +        help_menu = menubar.addMenu(_("&Help"))
       +        help_menu.addAction(_("&About"), self.show_about)
       +        help_menu.addAction(_("&Official website"), lambda: webbrowser.open("https://electrum.org"))
       +        help_menu.addSeparator()
       +        help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://docs.electrum.org/")).setShortcut(QKeySequence.HelpContents)
       +        help_menu.addAction(_("&Report Bug"), self.show_report_bug)
       +        help_menu.addSeparator()
       +        help_menu.addAction(_("&Donate to server"), self.donate_to_server)
       +
       +        self.setMenuBar(menubar)
       +
       +    def donate_to_server(self):
       +        d = self.network.get_donation_address()
       +        if d:
       +            host = self.network.get_parameters()[0]
       +            self.pay_to_URI('bitcoin:%s?message=donation for %s'%(d, host))
       +        else:
       +            self.show_error(_('No donation address for this server'))
       +
       +    def show_about(self):
       +        QMessageBox.about(self, "Electrum",
       +                          (_("Version")+" %s" % self.wallet.electrum_version + "\n\n" +
       +                           _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin.") + " " +
       +                           _("You do not need to perform regular backups, because your wallet can be "
       +                              "recovered from a secret phrase that you can memorize or write on paper.") + " " +
       +                           _("Startup times are instant because it operates in conjunction with high-performance "
       +                              "servers that handle the most complicated parts of the Bitcoin system.") + "\n\n" +
       +                           _("Uses icons from the Icons8 icon pack (icons8.com).")))
       +
       +    def show_report_bug(self):
       +        msg = ' '.join([
       +            _("Please report any bugs as issues on github:<br/>"),
       +            "<a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a><br/><br/>",
       +            _("Before reporting a bug, upgrade to the most recent version of Electrum (latest release or git HEAD), and include the version number in your report."),
       +            _("Try to explain not only what the bug is, but how it occurs.")
       +         ])
       +        self.show_message(msg, title="Electrum - " + _("Reporting Bugs"))
       +
       +    def notify_transactions(self):
       +        if not self.network or not self.network.is_connected():
       +            return
       +        self.print_error("Notifying GUI")
       +        if len(self.tx_notifications) > 0:
       +            # Combine the transactions if there are at least three
       +            num_txns = len(self.tx_notifications)
       +            if num_txns >= 3:
       +                total_amount = 0
       +                for tx in self.tx_notifications:
       +                    is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
       +                    if v > 0:
       +                        total_amount += v
       +                self.notify(_("{} new transactions received: Total amount received in the new transactions {}")
       +                            .format(num_txns, self.format_amount_and_units(total_amount)))
       +                self.tx_notifications = []
       +            else:
       +                for tx in self.tx_notifications:
       +                    if tx:
       +                        self.tx_notifications.remove(tx)
       +                        is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
       +                        if v > 0:
       +                            self.notify(_("New transaction received: {}").format(self.format_amount_and_units(v)))
       +
       +    def notify(self, message):
       +        if self.tray:
       +            try:
       +                # this requires Qt 5.9
       +                self.tray.showMessage("Electrum", message, QIcon(":icons/electrum_dark_icon"), 20000)
       +            except TypeError:
       +                self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
       +
       +
       +
       +    # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
       +    def getOpenFileName(self, title, filter = ""):
       +        directory = self.config.get('io_dir', os.path.expanduser('~'))
       +        fileName, __ = QFileDialog.getOpenFileName(self, title, directory, filter)
       +        if fileName and directory != os.path.dirname(fileName):
       +            self.config.set_key('io_dir', os.path.dirname(fileName), True)
       +        return fileName
       +
       +    def getSaveFileName(self, title, filename, filter = ""):
       +        directory = self.config.get('io_dir', os.path.expanduser('~'))
       +        path = os.path.join( directory, filename )
       +        fileName, __ = QFileDialog.getSaveFileName(self, title, path, filter)
       +        if fileName and directory != os.path.dirname(fileName):
       +            self.config.set_key('io_dir', os.path.dirname(fileName), True)
       +        return fileName
       +
       +    def connect_slots(self, sender):
       +        sender.timer_signal.connect(self.timer_actions)
       +
       +    def timer_actions(self):
       +        # Note this runs in the GUI thread
       +        if self.need_update.is_set():
       +            self.need_update.clear()
       +            self.update_wallet()
       +        # resolve aliases
       +        # FIXME this is a blocking network call that has a timeout of 5 sec
       +        self.payto_e.resolve()
       +        # update fee
       +        if self.require_fee_update:
       +            self.do_update_fee()
       +            self.require_fee_update = False
       +
       +    def format_amount(self, x, is_diff=False, whitespaces=False):
       +        return format_satoshis(x, self.num_zeros, self.decimal_point, is_diff=is_diff, whitespaces=whitespaces)
       +
       +    def format_amount_and_units(self, amount):
       +        text = self.format_amount(amount) + ' '+ self.base_unit()
       +        x = self.fx.format_amount_and_units(amount) if self.fx else None
       +        if text and x:
       +            text += ' (%s)'%x
       +        return text
       +
       +    def format_fee_rate(self, fee_rate):
       +        return format_fee_satoshis(fee_rate/1000, self.num_zeros) + ' sat/byte'
       +
       +    def get_decimal_point(self):
       +        return self.decimal_point
       +
       +    def base_unit(self):
       +        return decimal_point_to_base_unit_name(self.decimal_point)
       +
       +    def connect_fields(self, window, btc_e, fiat_e, fee_e):
       +
       +        def edit_changed(edit):
       +            if edit.follows:
       +                return
       +            edit.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
       +            fiat_e.is_last_edited = (edit == fiat_e)
       +            amount = edit.get_amount()
       +            rate = self.fx.exchange_rate() if self.fx else Decimal('NaN')
       +            if rate.is_nan() or amount is None:
       +                if edit is fiat_e:
       +                    btc_e.setText("")
       +                    if fee_e:
       +                        fee_e.setText("")
       +                else:
       +                    fiat_e.setText("")
       +            else:
       +                if edit is fiat_e:
       +                    btc_e.follows = True
       +                    btc_e.setAmount(int(amount / Decimal(rate) * COIN))
       +                    btc_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
       +                    btc_e.follows = False
       +                    if fee_e:
       +                        window.update_fee()
       +                else:
       +                    fiat_e.follows = True
       +                    fiat_e.setText(self.fx.ccy_amount_str(
       +                        amount * Decimal(rate) / COIN, False))
       +                    fiat_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
       +                    fiat_e.follows = False
       +
       +        btc_e.follows = False
       +        fiat_e.follows = False
       +        fiat_e.textChanged.connect(partial(edit_changed, fiat_e))
       +        btc_e.textChanged.connect(partial(edit_changed, btc_e))
       +        fiat_e.is_last_edited = False
       +
       +    def update_status(self):
       +        if not self.wallet:
       +            return
       +
       +        if self.network is None or not self.network.is_running():
       +            text = _("Offline")
       +            icon = QIcon(":icons/status_disconnected.png")
       +
       +        elif self.network.is_connected():
       +            server_height = self.network.get_server_height()
       +            server_lag = self.network.get_local_height() - server_height
       +            # Server height can be 0 after switching to a new server
       +            # until we get a headers subscription request response.
       +            # Display the synchronizing message in that case.
       +            if not self.wallet.up_to_date or server_height == 0:
       +                text = _("Synchronizing...")
       +                icon = QIcon(":icons/status_waiting.png")
       +            elif server_lag > 1:
       +                text = _("Server is lagging ({} blocks)").format(server_lag)
       +                icon = QIcon(":icons/status_lagging.png")
       +            else:
       +                c, u, x = self.wallet.get_balance()
       +                text =  _("Balance" ) + ": %s "%(self.format_amount_and_units(c))
       +                if u:
       +                    text +=  " [%s unconfirmed]"%(self.format_amount(u, is_diff=True).strip())
       +                if x:
       +                    text +=  " [%s unmatured]"%(self.format_amount(x, is_diff=True).strip())
       +
       +                # append fiat balance and price
       +                if self.fx.is_enabled():
       +                    text += self.fx.get_fiat_status_text(c + u + x,
       +                        self.base_unit(), self.get_decimal_point()) or ''
       +                if not self.network.proxy:
       +                    icon = QIcon(":icons/status_connected.png")
       +                else:
       +                    icon = QIcon(":icons/status_connected_proxy.png")
       +        else:
       +            if self.network.proxy:
       +                text = "{} ({})".format(_("Not connected"), _("proxy enabled"))
       +            else:
       +                text = _("Not connected")
       +            icon = QIcon(":icons/status_disconnected.png")
       +
       +        self.tray.setToolTip("%s (%s)" % (text, self.wallet.basename()))
       +        self.balance_label.setText(text)
       +        self.status_button.setIcon( icon )
       +
       +
       +    def update_wallet(self):
       +        self.update_status()
       +        if self.wallet.up_to_date or not self.network or not self.network.is_connected():
       +            self.update_tabs()
       +
       +    def update_tabs(self):
       +        self.history_list.update()
       +        self.request_list.update()
       +        self.address_list.update()
       +        self.utxo_list.update()
       +        self.contact_list.update()
       +        self.invoice_list.update()
       +        self.update_completions()
       +
       +    def create_history_tab(self):
       +        from .history_list import HistoryList
       +        self.history_list = l = HistoryList(self)
       +        l.searchable_list = l
       +        toolbar = l.create_toolbar(self.config)
       +        toolbar_shown = self.config.get('show_toolbar_history', False)
       +        l.show_toolbar(toolbar_shown)
       +        return self.create_list_tab(l, toolbar)
       +
       +    def show_address(self, addr):
       +        from . import address_dialog
       +        d = address_dialog.AddressDialog(self, addr)
       +        d.exec_()
       +
       +    def show_transaction(self, tx, tx_desc = None):
       +        '''tx_desc is set only for txs created in the Send tab'''
       +        show_transaction(tx, self, tx_desc)
       +
       +    def create_receive_tab(self):
       +        # A 4-column grid layout.  All the stretch is in the last column.
       +        # The exchange rate plugin adds a fiat widget in column 2
       +        self.receive_grid = grid = QGridLayout()
       +        grid.setSpacing(8)
       +        grid.setColumnStretch(3, 1)
       +
       +        self.receive_address_e = ButtonsLineEdit()
       +        self.receive_address_e.addCopyButton(self.app)
       +        self.receive_address_e.setReadOnly(True)
       +        msg = _('Bitcoin address where the payment should be received. Note that each payment request uses a different Bitcoin address.')
       +        self.receive_address_label = HelpLabel(_('Receiving address'), msg)
       +        self.receive_address_e.textChanged.connect(self.update_receive_qr)
       +        self.receive_address_e.setFocusPolicy(Qt.ClickFocus)
       +        grid.addWidget(self.receive_address_label, 0, 0)
       +        grid.addWidget(self.receive_address_e, 0, 1, 1, -1)
       +
       +        self.receive_message_e = QLineEdit()
       +        grid.addWidget(QLabel(_('Description')), 1, 0)
       +        grid.addWidget(self.receive_message_e, 1, 1, 1, -1)
       +        self.receive_message_e.textChanged.connect(self.update_receive_qr)
       +
       +        self.receive_amount_e = BTCAmountEdit(self.get_decimal_point)
       +        grid.addWidget(QLabel(_('Requested amount')), 2, 0)
       +        grid.addWidget(self.receive_amount_e, 2, 1)
       +        self.receive_amount_e.textChanged.connect(self.update_receive_qr)
       +
       +        self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '')
       +        if not self.fx or not self.fx.is_enabled():
       +            self.fiat_receive_e.setVisible(False)
       +        grid.addWidget(self.fiat_receive_e, 2, 2, Qt.AlignLeft)
       +        self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)
       +
       +        self.expires_combo = QComboBox()
       +        self.expires_combo.addItems([i[0] for i in expiration_values])
       +        self.expires_combo.setCurrentIndex(3)
       +        self.expires_combo.setFixedWidth(self.receive_amount_e.width())
       +        msg = ' '.join([
       +            _('Expiration date of your request.'),
       +            _('This information is seen by the recipient if you send them a signed payment request.'),
       +            _('Expired requests have to be deleted manually from your list, in order to free the corresponding Bitcoin addresses.'),
       +            _('The bitcoin address never expires and will always be part of this electrum wallet.'),
       +        ])
       +        grid.addWidget(HelpLabel(_('Request expires'), msg), 3, 0)
       +        grid.addWidget(self.expires_combo, 3, 1)
       +        self.expires_label = QLineEdit('')
       +        self.expires_label.setReadOnly(1)
       +        self.expires_label.setFocusPolicy(Qt.NoFocus)
       +        self.expires_label.hide()
       +        grid.addWidget(self.expires_label, 3, 1)
       +
       +        self.save_request_button = QPushButton(_('Save'))
       +        self.save_request_button.clicked.connect(self.save_payment_request)
       +
       +        self.new_request_button = QPushButton(_('New'))
       +        self.new_request_button.clicked.connect(self.new_payment_request)
       +
       +        self.receive_qr = QRCodeWidget(fixedSize=200)
       +        self.receive_qr.mouseReleaseEvent = lambda x: self.toggle_qr_window()
       +        self.receive_qr.enterEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))
       +        self.receive_qr.leaveEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))
       +
       +        self.receive_buttons = buttons = QHBoxLayout()
       +        buttons.addStretch(1)
       +        buttons.addWidget(self.save_request_button)
       +        buttons.addWidget(self.new_request_button)
       +        grid.addLayout(buttons, 4, 1, 1, 2)
       +
       +        self.receive_requests_label = QLabel(_('Requests'))
       +
       +        from .request_list import RequestList
       +        self.request_list = RequestList(self)
       +
       +        # layout
       +        vbox_g = QVBoxLayout()
       +        vbox_g.addLayout(grid)
       +        vbox_g.addStretch()
       +
       +        hbox = QHBoxLayout()
       +        hbox.addLayout(vbox_g)
       +        hbox.addWidget(self.receive_qr)
       +
       +        w = QWidget()
       +        w.searchable_list = self.request_list
       +        vbox = QVBoxLayout(w)
       +        vbox.addLayout(hbox)
       +        vbox.addStretch(1)
       +        vbox.addWidget(self.receive_requests_label)
       +        vbox.addWidget(self.request_list)
       +        vbox.setStretchFactor(self.request_list, 1000)
       +
       +        return w
       +
       +
       +    def delete_payment_request(self, addr):
       +        self.wallet.remove_payment_request(addr, self.config)
       +        self.request_list.update()
       +        self.clear_receive_tab()
       +
       +    def get_request_URI(self, addr):
       +        req = self.wallet.receive_requests[addr]
       +        message = self.wallet.labels.get(addr, '')
       +        amount = req['amount']
       +        URI = util.create_URI(addr, amount, message)
       +        if req.get('time'):
       +            URI += "&time=%d"%req.get('time')
       +        if req.get('exp'):
       +            URI += "&exp=%d"%req.get('exp')
       +        if req.get('name') and req.get('sig'):
       +            sig = bfh(req.get('sig'))
       +            sig = bitcoin.base_encode(sig, base=58)
       +            URI += "&name=" + req['name'] + "&sig="+sig
       +        return str(URI)
       +
       +
       +    def sign_payment_request(self, addr):
       +        alias = self.config.get('alias')
       +        alias_privkey = None
       +        if alias and self.alias_info:
       +            alias_addr, alias_name, validated = self.alias_info
       +            if alias_addr:
       +                if self.wallet.is_mine(alias_addr):
       +                    msg = _('This payment request will be signed.') + '\n' + _('Please enter your password')
       +                    password = None
       +                    if self.wallet.has_keystore_encryption():
       +                        password = self.password_dialog(msg)
       +                        if not password:
       +                            return
       +                    try:
       +                        self.wallet.sign_payment_request(addr, alias, alias_addr, password)
       +                    except Exception as e:
       +                        self.show_error(str(e))
       +                        return
       +                else:
       +                    return
       +
       +    def save_payment_request(self):
       +        addr = str(self.receive_address_e.text())
       +        amount = self.receive_amount_e.get_amount()
       +        message = self.receive_message_e.text()
       +        if not message and not amount:
       +            self.show_error(_('No message or amount'))
       +            return False
       +        i = self.expires_combo.currentIndex()
       +        expiration = list(map(lambda x: x[1], expiration_values))[i]
       +        req = self.wallet.make_payment_request(addr, amount, message, expiration)
       +        try:
       +            self.wallet.add_payment_request(req, self.config)
       +        except Exception as e:
       +            traceback.print_exc(file=sys.stderr)
       +            self.show_error(_('Error adding payment request') + ':\n' + str(e))
       +        else:
       +            self.sign_payment_request(addr)
       +            self.save_request_button.setEnabled(False)
       +        finally:
       +            self.request_list.update()
       +            self.address_list.update()
       +
       +    def view_and_paste(self, title, msg, data):
       +        dialog = WindowModalDialog(self, title)
       +        vbox = QVBoxLayout()
       +        label = QLabel(msg)
       +        label.setWordWrap(True)
       +        vbox.addWidget(label)
       +        pr_e = ShowQRTextEdit(text=data)
       +        vbox.addWidget(pr_e)
       +        vbox.addLayout(Buttons(CopyCloseButton(pr_e.text, self.app, dialog)))
       +        dialog.setLayout(vbox)
       +        dialog.exec_()
       +
       +    def export_payment_request(self, addr):
       +        r = self.wallet.receive_requests.get(addr)
       +        pr = paymentrequest.serialize_request(r).SerializeToString()
       +        name = r['id'] + '.bip70'
       +        fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
       +        if fileName:
       +            with open(fileName, "wb+") as f:
       +                f.write(util.to_bytes(pr))
       +            self.show_message(_("Request saved successfully"))
       +            self.saved = True
       +
       +    def new_payment_request(self):
       +        addr = self.wallet.get_unused_address()
       +        if addr is None:
       +            if not self.wallet.is_deterministic():
       +                msg = [
       +                    _('No more addresses in your wallet.'),
       +                    _('You are using a non-deterministic wallet, which cannot create new addresses.'),
       +                    _('If you want to create new addresses, use a deterministic wallet instead.')
       +                   ]
       +                self.show_message(' '.join(msg))
       +                return
       +            if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")):
       +                return
       +            addr = self.wallet.create_new_address(False)
       +        self.set_receive_address(addr)
       +        self.expires_label.hide()
       +        self.expires_combo.show()
       +        self.new_request_button.setEnabled(False)
       +        self.receive_message_e.setFocus(1)
       +
       +    def set_receive_address(self, addr):
       +        self.receive_address_e.setText(addr)
       +        self.receive_message_e.setText('')
       +        self.receive_amount_e.setAmount(None)
       +
       +    def clear_receive_tab(self):
       +        addr = self.wallet.get_receiving_address() or ''
       +        self.receive_address_e.setText(addr)
       +        self.receive_message_e.setText('')
       +        self.receive_amount_e.setAmount(None)
       +        self.expires_label.hide()
       +        self.expires_combo.show()
       +
       +    def toggle_qr_window(self):
       +        from . import qrwindow
       +        if not self.qr_window:
       +            self.qr_window = qrwindow.QR_Window(self)
       +            self.qr_window.setVisible(True)
       +            self.qr_window_geometry = self.qr_window.geometry()
       +        else:
       +            if not self.qr_window.isVisible():
       +                self.qr_window.setVisible(True)
       +                self.qr_window.setGeometry(self.qr_window_geometry)
       +            else:
       +                self.qr_window_geometry = self.qr_window.geometry()
       +                self.qr_window.setVisible(False)
       +        self.update_receive_qr()
       +
       +    def show_send_tab(self):
       +        self.tabs.setCurrentIndex(self.tabs.indexOf(self.send_tab))
       +
       +    def show_receive_tab(self):
       +        self.tabs.setCurrentIndex(self.tabs.indexOf(self.receive_tab))
       +
       +    def receive_at(self, addr):
       +        if not bitcoin.is_address(addr):
       +            return
       +        self.show_receive_tab()
       +        self.receive_address_e.setText(addr)
       +        self.new_request_button.setEnabled(True)
       +
       +    def update_receive_qr(self):
       +        addr = str(self.receive_address_e.text())
       +        amount = self.receive_amount_e.get_amount()
       +        message = self.receive_message_e.text()
       +        self.save_request_button.setEnabled((amount is not None) or (message != ""))
       +        uri = util.create_URI(addr, amount, message)
       +        self.receive_qr.setData(uri)
       +        if self.qr_window and self.qr_window.isVisible():
       +            self.qr_window.set_content(addr, amount, message, uri)
       +
       +    def set_feerounding_text(self, num_satoshis_added):
       +        self.feerounding_text = (_('Additional {} satoshis are going to be added.')
       +                                 .format(num_satoshis_added))
       +
       +    def create_send_tab(self):
       +        # A 4-column grid layout.  All the stretch is in the last column.
       +        # The exchange rate plugin adds a fiat widget in column 2
       +        self.send_grid = grid = QGridLayout()
       +        grid.setSpacing(8)
       +        grid.setColumnStretch(3, 1)
       +
       +        from .paytoedit import PayToEdit
       +        self.amount_e = BTCAmountEdit(self.get_decimal_point)
       +        self.payto_e = PayToEdit(self)
       +        msg = _('Recipient of the funds.') + '\n\n'\
       +              + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')
       +        payto_label = HelpLabel(_('Pay to'), msg)
       +        grid.addWidget(payto_label, 1, 0)
       +        grid.addWidget(self.payto_e, 1, 1, 1, -1)
       +
       +        completer = QCompleter()
       +        completer.setCaseSensitivity(False)
       +        self.payto_e.set_completer(completer)
       +        completer.setModel(self.completions)
       +
       +        msg = _('Description of the transaction (not mandatory).') + '\n\n'\
       +              + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')
       +        description_label = HelpLabel(_('Description'), msg)
       +        grid.addWidget(description_label, 2, 0)
       +        self.message_e = MyLineEdit()
       +        grid.addWidget(self.message_e, 2, 1, 1, -1)
       +
       +        self.from_label = QLabel(_('From'))
       +        grid.addWidget(self.from_label, 3, 0)
       +        self.from_list = MyTreeWidget(self, self.from_list_menu, ['',''])
       +        self.from_list.setHeaderHidden(True)
       +        self.from_list.setMaximumHeight(80)
       +        grid.addWidget(self.from_list, 3, 1, 1, -1)
       +        self.set_pay_from([])
       +
       +        msg = _('Amount to be sent.') + '\n\n' \
       +              + _('The amount will be displayed in red if you do not have enough funds in your wallet.') + ' ' \
       +              + _('Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') + '\n\n' \
       +              + _('Keyboard shortcut: type "!" to send all your coins.')
       +        amount_label = HelpLabel(_('Amount'), msg)
       +        grid.addWidget(amount_label, 4, 0)
       +        grid.addWidget(self.amount_e, 4, 1)
       +
       +        self.fiat_send_e = AmountEdit(self.fx.get_currency if self.fx else '')
       +        if not self.fx or not self.fx.is_enabled():
       +            self.fiat_send_e.setVisible(False)
       +        grid.addWidget(self.fiat_send_e, 4, 2)
       +        self.amount_e.frozen.connect(
       +            lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
       +
       +        self.max_button = EnterButton(_("Max"), self.spend_max)
       +        self.max_button.setFixedWidth(140)
       +        grid.addWidget(self.max_button, 4, 3)
       +        hbox = QHBoxLayout()
       +        hbox.addStretch(1)
       +        grid.addLayout(hbox, 4, 4)
       +
       +        msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
       +              + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
       +              + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')
       +        self.fee_e_label = HelpLabel(_('Fee'), msg)
       +
       +        def fee_cb(dyn, pos, fee_rate):
       +            if dyn:
       +                if self.config.use_mempool_fees():
       +                    self.config.set_key('depth_level', pos, False)
       +                else:
       +                    self.config.set_key('fee_level', pos, False)
       +            else:
       +                self.config.set_key('fee_per_kb', fee_rate, False)
       +
       +            if fee_rate:
       +                fee_rate = Decimal(fee_rate)
       +                self.feerate_e.setAmount(quantize_feerate(fee_rate / 1000))
       +            else:
       +                self.feerate_e.setAmount(None)
       +            self.fee_e.setModified(False)
       +
       +            self.fee_slider.activate()
       +            self.spend_max() if self.is_max else self.update_fee()
       +
       +        self.fee_slider = FeeSlider(self, self.config, fee_cb)
       +        self.fee_slider.setFixedWidth(140)
       +
       +        def on_fee_or_feerate(edit_changed, editing_finished):
       +            edit_other = self.feerate_e if edit_changed == self.fee_e else self.fee_e
       +            if editing_finished:
       +                if edit_changed.get_amount() is None:
       +                    # This is so that when the user blanks the fee and moves on,
       +                    # we go back to auto-calculate mode and put a fee back.
       +                    edit_changed.setModified(False)
       +            else:
       +                # edit_changed was edited just now, so make sure we will
       +                # freeze the correct fee setting (this)
       +                edit_other.setModified(False)
       +            self.fee_slider.deactivate()
       +            self.update_fee()
       +
       +        class TxSizeLabel(QLabel):
       +            def setAmount(self, byte_size):
       +                self.setText(('x   %s bytes   =' % byte_size) if byte_size else '')
       +
       +        self.size_e = TxSizeLabel()
       +        self.size_e.setAlignment(Qt.AlignCenter)
       +        self.size_e.setAmount(0)
       +        self.size_e.setFixedWidth(140)
       +        self.size_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
       +
       +        self.feerate_e = FeerateEdit(lambda: 0)
       +        self.feerate_e.setAmount(self.config.fee_per_byte())
       +        self.feerate_e.textEdited.connect(partial(on_fee_or_feerate, self.feerate_e, False))
       +        self.feerate_e.editingFinished.connect(partial(on_fee_or_feerate, self.feerate_e, True))
       +
       +        self.fee_e = BTCAmountEdit(self.get_decimal_point)
       +        self.fee_e.textEdited.connect(partial(on_fee_or_feerate, self.fee_e, False))
       +        self.fee_e.editingFinished.connect(partial(on_fee_or_feerate, self.fee_e, True))
       +
       +        def feerounding_onclick():
       +            text = (self.feerounding_text + '\n\n' +
       +                    _('To somewhat protect your privacy, Electrum tries to create change with similar precision to other outputs.') + ' ' +
       +                    _('At most 100 satoshis might be lost due to this rounding.') + ' ' +
       +                    _("You can disable this setting in '{}'.").format(_('Preferences')) + '\n' +
       +                    _('Also, dust is not kept as change, but added to the fee.'))
       +            QMessageBox.information(self, 'Fee rounding', text)
       +
       +        self.feerounding_icon = QPushButton(QIcon(':icons/info.png'), '')
       +        self.feerounding_icon.setFixedWidth(20)
       +        self.feerounding_icon.setFlat(True)
       +        self.feerounding_icon.clicked.connect(feerounding_onclick)
       +        self.feerounding_icon.setVisible(False)
       +
       +        self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e)
       +
       +        vbox_feelabel = QVBoxLayout()
       +        vbox_feelabel.addWidget(self.fee_e_label)
       +        vbox_feelabel.addStretch(1)
       +        grid.addLayout(vbox_feelabel, 5, 0)
       +
       +        self.fee_adv_controls = QWidget()
       +        hbox = QHBoxLayout(self.fee_adv_controls)
       +        hbox.setContentsMargins(0, 0, 0, 0)
       +        hbox.addWidget(self.feerate_e)
       +        hbox.addWidget(self.size_e)
       +        hbox.addWidget(self.fee_e)
       +        hbox.addWidget(self.feerounding_icon, Qt.AlignLeft)
       +        hbox.addStretch(1)
       +
       +        vbox_feecontrol = QVBoxLayout()
       +        vbox_feecontrol.addWidget(self.fee_adv_controls)
       +        vbox_feecontrol.addWidget(self.fee_slider)
       +
       +        grid.addLayout(vbox_feecontrol, 5, 1, 1, -1)
       +
       +        if not self.config.get('show_fee', False):
       +            self.fee_adv_controls.setVisible(False)
       +
       +        self.preview_button = EnterButton(_("Preview"), self.do_preview)
       +        self.preview_button.setToolTip(_('Display the details of your transaction before signing it.'))
       +        self.send_button = EnterButton(_("Send"), self.do_send)
       +        self.clear_button = EnterButton(_("Clear"), self.do_clear)
       +        buttons = QHBoxLayout()
       +        buttons.addStretch(1)
       +        buttons.addWidget(self.clear_button)
       +        buttons.addWidget(self.preview_button)
       +        buttons.addWidget(self.send_button)
       +        grid.addLayout(buttons, 6, 1, 1, 3)
       +
       +        self.amount_e.shortcut.connect(self.spend_max)
       +        self.payto_e.textChanged.connect(self.update_fee)
       +        self.amount_e.textEdited.connect(self.update_fee)
       +
       +        def reset_max(text):
       +            self.is_max = False
       +            enable = not bool(text) and not self.amount_e.isReadOnly()
       +            self.max_button.setEnabled(enable)
       +        self.amount_e.textEdited.connect(reset_max)
       +        self.fiat_send_e.textEdited.connect(reset_max)
       +
       +        def entry_changed():
       +            text = ""
       +
       +            amt_color = ColorScheme.DEFAULT
       +            fee_color = ColorScheme.DEFAULT
       +            feerate_color = ColorScheme.DEFAULT
       +
       +            if self.not_enough_funds:
       +                amt_color, fee_color = ColorScheme.RED, ColorScheme.RED
       +                feerate_color = ColorScheme.RED
       +                text = _( "Not enough funds" )
       +                c, u, x = self.wallet.get_frozen_balance()
       +                if c+u+x:
       +                    text += ' (' + self.format_amount(c+u+x).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
       +
       +            # blue color denotes auto-filled values
       +            elif self.fee_e.isModified():
       +                feerate_color = ColorScheme.BLUE
       +            elif self.feerate_e.isModified():
       +                fee_color = ColorScheme.BLUE
       +            elif self.amount_e.isModified():
       +                fee_color = ColorScheme.BLUE
       +                feerate_color = ColorScheme.BLUE
       +            else:
       +                amt_color = ColorScheme.BLUE
       +                fee_color = ColorScheme.BLUE
       +                feerate_color = ColorScheme.BLUE
       +
       +            self.statusBar().showMessage(text)
       +            self.amount_e.setStyleSheet(amt_color.as_stylesheet())
       +            self.fee_e.setStyleSheet(fee_color.as_stylesheet())
       +            self.feerate_e.setStyleSheet(feerate_color.as_stylesheet())
       +
       +        self.amount_e.textChanged.connect(entry_changed)
       +        self.fee_e.textChanged.connect(entry_changed)
       +        self.feerate_e.textChanged.connect(entry_changed)
       +
       +        self.invoices_label = QLabel(_('Invoices'))
       +        from .invoice_list import InvoiceList
       +        self.invoice_list = InvoiceList(self)
       +
       +        vbox0 = QVBoxLayout()
       +        vbox0.addLayout(grid)
       +        hbox = QHBoxLayout()
       +        hbox.addLayout(vbox0)
       +        w = QWidget()
       +        vbox = QVBoxLayout(w)
       +        vbox.addLayout(hbox)
       +        vbox.addStretch(1)
       +        vbox.addWidget(self.invoices_label)
       +        vbox.addWidget(self.invoice_list)
       +        vbox.setStretchFactor(self.invoice_list, 1000)
       +        w.searchable_list = self.invoice_list
       +        run_hook('create_send_tab', grid)
       +        return w
       +
       +    def spend_max(self):
       +        if run_hook('abort_send', self):
       +            return
       +        self.is_max = True
       +        self.do_update_fee()
       +
       +    def update_fee(self):
       +        self.require_fee_update = True
       +
       +    def get_payto_or_dummy(self):
       +        r = self.payto_e.get_recipient()
       +        if r:
       +            return r
       +        return (TYPE_ADDRESS, self.wallet.dummy_address())
       +
       +    def do_update_fee(self):
       +        '''Recalculate the fee.  If the fee was manually input, retain it, but
       +        still build the TX to see if there are enough funds.
       +        '''
       +        freeze_fee = self.is_send_fee_frozen()
       +        freeze_feerate = self.is_send_feerate_frozen()
       +        amount = '!' if self.is_max else self.amount_e.get_amount()
       +        if amount is None:
       +            if not freeze_fee:
       +                self.fee_e.setAmount(None)
       +            self.not_enough_funds = False
       +            self.statusBar().showMessage('')
       +        else:
       +            fee_estimator = self.get_send_fee_estimator()
       +            outputs = self.payto_e.get_outputs(self.is_max)
       +            if not outputs:
       +                _type, addr = self.get_payto_or_dummy()
       +                outputs = [(_type, addr, amount)]
       +            is_sweep = bool(self.tx_external_keypairs)
       +            make_tx = lambda fee_est: \
       +                self.wallet.make_unsigned_transaction(
       +                    self.get_coins(), outputs, self.config,
       +                    fixed_fee=fee_est, is_sweep=is_sweep)
       +            try:
       +                tx = make_tx(fee_estimator)
       +                self.not_enough_funds = False
       +            except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
       +                if not freeze_fee:
       +                    self.fee_e.setAmount(None)
       +                if not freeze_feerate:
       +                    self.feerate_e.setAmount(None)
       +                self.feerounding_icon.setVisible(False)
       +
       +                if isinstance(e, NotEnoughFunds):
       +                    self.not_enough_funds = True
       +                elif isinstance(e, NoDynamicFeeEstimates):
       +                    try:
       +                        tx = make_tx(0)
       +                        size = tx.estimated_size()
       +                        self.size_e.setAmount(size)
       +                    except BaseException:
       +                        pass
       +                return
       +            except BaseException:
       +                traceback.print_exc(file=sys.stderr)
       +                return
       +
       +            size = tx.estimated_size()
       +            self.size_e.setAmount(size)
       +
       +            fee = tx.get_fee()
       +            fee = None if self.not_enough_funds else fee
       +
       +            # Displayed fee/fee_rate values are set according to user input.
       +            # Due to rounding or dropping dust in CoinChooser,
       +            # actual fees often differ somewhat.
       +            if freeze_feerate or self.fee_slider.is_active():
       +                displayed_feerate = self.feerate_e.get_amount()
       +                if displayed_feerate is not None:
       +                    displayed_feerate = quantize_feerate(displayed_feerate)
       +                else:
       +                    # fallback to actual fee
       +                    displayed_feerate = quantize_feerate(fee / size) if fee is not None else None
       +                    self.feerate_e.setAmount(displayed_feerate)
       +                displayed_fee = round(displayed_feerate * size) if displayed_feerate is not None else None
       +                self.fee_e.setAmount(displayed_fee)
       +            else:
       +                if freeze_fee:
       +                    displayed_fee = self.fee_e.get_amount()
       +                else:
       +                    # fallback to actual fee if nothing is frozen
       +                    displayed_fee = fee
       +                    self.fee_e.setAmount(displayed_fee)
       +                displayed_fee = displayed_fee if displayed_fee else 0
       +                displayed_feerate = quantize_feerate(displayed_fee / size) if displayed_fee is not None else None
       +                self.feerate_e.setAmount(displayed_feerate)
       +
       +            # show/hide fee rounding icon
       +            feerounding = (fee - displayed_fee) if fee else 0
       +            self.set_feerounding_text(int(feerounding))
       +            self.feerounding_icon.setToolTip(self.feerounding_text)
       +            self.feerounding_icon.setVisible(abs(feerounding) >= 1)
       +
       +            if self.is_max:
       +                amount = tx.output_value()
       +                __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
       +                amount_after_all_fees = amount - x_fee_amount
       +                self.amount_e.setAmount(amount_after_all_fees)
       +
       +    def from_list_delete(self, item):
       +        i = self.from_list.indexOfTopLevelItem(item)
       +        self.pay_from.pop(i)
       +        self.redraw_from_list()
       +        self.update_fee()
       +
       +    def from_list_menu(self, position):
       +        item = self.from_list.itemAt(position)
       +        menu = QMenu()
       +        menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
       +        menu.exec_(self.from_list.viewport().mapToGlobal(position))
       +
       +    def set_pay_from(self, coins):
       +        self.pay_from = list(coins)
       +        self.redraw_from_list()
       +
       +    def redraw_from_list(self):
       +        self.from_list.clear()
       +        self.from_label.setHidden(len(self.pay_from) == 0)
       +        self.from_list.setHidden(len(self.pay_from) == 0)
       +
       +        def format(x):
       +            h = x.get('prevout_hash')
       +            return h[0:10] + '...' + h[-10:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
       +
       +        for item in self.pay_from:
       +            self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
       +
       +    def get_contact_payto(self, key):
       +        _type, label = self.contacts.get(key)
       +        return label + '  <' + key + '>' if _type == 'address' else key
       +
       +    def update_completions(self):
       +        l = [self.get_contact_payto(key) for key in self.contacts.keys()]
       +        self.completions.setStringList(l)
       +
       +    def protected(func):
       +        '''Password request wrapper.  The password is passed to the function
       +        as the 'password' named argument.  "None" indicates either an
       +        unencrypted wallet, or the user cancelled the password request.
       +        An empty input is passed as the empty string.'''
       +        def request_password(self, *args, **kwargs):
       +            parent = self.top_level_window()
       +            password = None
       +            while self.wallet.has_keystore_encryption():
       +                password = self.password_dialog(parent=parent)
       +                if password is None:
       +                    # User cancelled password input
       +                    return
       +                try:
       +                    self.wallet.check_password(password)
       +                    break
       +                except Exception as e:
       +                    self.show_error(str(e), parent=parent)
       +                    continue
       +
       +            kwargs['password'] = password
       +            return func(self, *args, **kwargs)
       +        return request_password
       +
       +    def is_send_fee_frozen(self):
       +        return self.fee_e.isVisible() and self.fee_e.isModified() \
       +               and (self.fee_e.text() or self.fee_e.hasFocus())
       +
       +    def is_send_feerate_frozen(self):
       +        return self.feerate_e.isVisible() and self.feerate_e.isModified() \
       +               and (self.feerate_e.text() or self.feerate_e.hasFocus())
       +
       +    def get_send_fee_estimator(self):
       +        if self.is_send_fee_frozen():
       +            fee_estimator = self.fee_e.get_amount()
       +        elif self.is_send_feerate_frozen():
       +            amount = self.feerate_e.get_amount()  # sat/byte feerate
       +            amount = 0 if amount is None else amount * 1000  # sat/kilobyte feerate
       +            fee_estimator = partial(
       +                simple_config.SimpleConfig.estimate_fee_for_feerate, amount)
       +        else:
       +            fee_estimator = None
       +        return fee_estimator
       +
       +    def read_send_tab(self):
       +        if self.payment_request and self.payment_request.has_expired():
       +            self.show_error(_('Payment request has expired'))
       +            return
       +        label = self.message_e.text()
       +
       +        if self.payment_request:
       +            outputs = self.payment_request.get_outputs()
       +        else:
       +            errors = self.payto_e.get_errors()
       +            if errors:
       +                self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors]))
       +                return
       +            outputs = self.payto_e.get_outputs(self.is_max)
       +
       +            if self.payto_e.is_alias and self.payto_e.validated is False:
       +                alias = self.payto_e.toPlainText()
       +                msg = _('WARNING: the alias "{}" could not be validated via an additional '
       +                        'security check, DNSSEC, and thus may not be correct.').format(alias) + '\n'
       +                msg += _('Do you wish to continue?')
       +                if not self.question(msg):
       +                    return
       +
       +        if not outputs:
       +            self.show_error(_('No outputs'))
       +            return
       +
       +        for _type, addr, amount in outputs:
       +            if addr is None:
       +                self.show_error(_('Bitcoin Address is None'))
       +                return
       +            if _type == TYPE_ADDRESS and not bitcoin.is_address(addr):
       +                self.show_error(_('Invalid Bitcoin Address'))
       +                return
       +            if amount is None:
       +                self.show_error(_('Invalid Amount'))
       +                return
       +
       +        fee_estimator = self.get_send_fee_estimator()
       +        coins = self.get_coins()
       +        return outputs, fee_estimator, label, coins
       +
       +    def do_preview(self):
       +        self.do_send(preview = True)
       +
       +    def do_send(self, preview = False):
       +        if run_hook('abort_send', self):
       +            return
       +        r = self.read_send_tab()
       +        if not r:
       +            return
       +        outputs, fee_estimator, tx_desc, coins = r
       +        try:
       +            is_sweep = bool(self.tx_external_keypairs)
       +            tx = self.wallet.make_unsigned_transaction(
       +                coins, outputs, self.config, fixed_fee=fee_estimator,
       +                is_sweep=is_sweep)
       +        except NotEnoughFunds:
       +            self.show_message(_("Insufficient funds"))
       +            return
       +        except BaseException as e:
       +            traceback.print_exc(file=sys.stdout)
       +            self.show_message(str(e))
       +            return
       +
       +        amount = tx.output_value() if self.is_max else sum(map(lambda x:x[2], outputs))
       +        fee = tx.get_fee()
       +
       +        use_rbf = self.config.get('use_rbf', True)
       +        if use_rbf:
       +            tx.set_rbf(True)
       +
       +        if fee < self.wallet.relayfee() * tx.estimated_size() / 1000:
       +            self.show_error('\n'.join([
       +                _("This transaction requires a higher fee, or it will not be propagated by your current server"),
       +                _("Try to raise your transaction fee, or use a server with a lower relay fee.")
       +            ]))
       +            return
       +
       +        if preview:
       +            self.show_transaction(tx, tx_desc)
       +            return
       +
       +        if not self.network:
       +            self.show_error(_("You can't broadcast a transaction without a live network connection."))
       +            return
       +
       +        # confirmation dialog
       +        msg = [
       +            _("Amount to be sent") + ": " + self.format_amount_and_units(amount),
       +            _("Mining fee") + ": " + self.format_amount_and_units(fee),
       +        ]
       +
       +        x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
       +        if x_fee:
       +            x_fee_address, x_fee_amount = x_fee
       +            msg.append( _("Additional fees") + ": " + self.format_amount_and_units(x_fee_amount) )
       +
       +        confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
       +        if fee > confirm_rate * tx.estimated_size() / 1000:
       +            msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
       +
       +        if self.wallet.has_keystore_encryption():
       +            msg.append("")
       +            msg.append(_("Enter your password to proceed"))
       +            password = self.password_dialog('\n'.join(msg))
       +            if not password:
       +                return
       +        else:
       +            msg.append(_('Proceed?'))
       +            password = None
       +            if not self.question('\n'.join(msg)):
       +                return
       +
       +        def sign_done(success):
       +            if success:
       +                if not tx.is_complete():
       +                    self.show_transaction(tx)
       +                    self.do_clear()
       +                else:
       +                    self.broadcast_transaction(tx, tx_desc)
       +        self.sign_tx_with_password(tx, sign_done, password)
       +
       +    @protected
       +    def sign_tx(self, tx, callback, password):
       +        self.sign_tx_with_password(tx, callback, password)
       +
       +    def sign_tx_with_password(self, tx, callback, password):
       +        '''Sign the transaction in a separate thread.  When done, calls
       +        the callback with a success code of True or False.
       +        '''
       +        def on_success(result):
       +            callback(True)
       +        def on_failure(exc_info):
       +            self.on_error(exc_info)
       +            callback(False)
       +        on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
       +        if self.tx_external_keypairs:
       +            # can sign directly
       +            task = partial(Transaction.sign, tx, self.tx_external_keypairs)
       +        else:
       +            task = partial(self.wallet.sign_transaction, tx, password)
       +        msg = _('Signing transaction...')
       +        WaitingDialog(self, msg, task, on_success, on_failure)
       +
       +    def broadcast_transaction(self, tx, tx_desc):
       +
       +        def broadcast_thread():
       +            # non-GUI thread
       +            pr = self.payment_request
       +            if pr and pr.has_expired():
       +                self.payment_request = None
       +                return False, _("Payment request has expired")
       +            status, msg = self.network.broadcast_transaction(tx)
       +            if pr and status is True:
       +                self.invoices.set_paid(pr, tx.txid())
       +                self.invoices.save()
       +                self.payment_request = None
       +                refund_address = self.wallet.get_receiving_addresses()[0]
       +                ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
       +                if ack_status:
       +                    msg = ack_msg
       +            return status, msg
       +
       +        # Capture current TL window; override might be removed on return
       +        parent = self.top_level_window(lambda win: isinstance(win, MessageBoxMixin))
       +
       +        def broadcast_done(result):
       +            # GUI thread
       +            if result:
       +                status, msg = result
       +                if status:
       +                    if tx_desc is not None and tx.is_complete():
       +                        self.wallet.set_label(tx.txid(), tx_desc)
       +                    parent.show_message(_('Payment sent.') + '\n' + msg)
       +                    self.invoice_list.update()
       +                    self.do_clear()
       +                else:
       +                    parent.show_error(msg)
       +
       +        WaitingDialog(self, _('Broadcasting transaction...'),
       +                      broadcast_thread, broadcast_done, self.on_error)
       +
       +    def query_choice(self, msg, choices):
       +        # Needed by QtHandler for hardware wallets
       +        dialog = WindowModalDialog(self.top_level_window())
       +        clayout = ChoicesLayout(msg, choices)
       +        vbox = QVBoxLayout(dialog)
       +        vbox.addLayout(clayout.layout())
       +        vbox.addLayout(Buttons(OkButton(dialog)))
       +        if not dialog.exec_():
       +            return None
       +        return clayout.selected_index()
       +
       +    def lock_amount(self, b):
       +        self.amount_e.setFrozen(b)
       +        self.max_button.setEnabled(not b)
       +
       +    def prepare_for_payment_request(self):
       +        self.show_send_tab()
       +        self.payto_e.is_pr = True
       +        for e in [self.payto_e, self.message_e]:
       +            e.setFrozen(True)
       +        self.lock_amount(True)
       +        self.payto_e.setText(_("please wait..."))
       +        return True
       +
       +    def delete_invoice(self, key):
       +        self.invoices.remove(key)
       +        self.invoice_list.update()
       +
       +    def payment_request_ok(self):
       +        pr = self.payment_request
       +        key = self.invoices.add(pr)
       +        status = self.invoices.get_status(key)
       +        self.invoice_list.update()
       +        if status == PR_PAID:
       +            self.show_message("invoice already paid")
       +            self.do_clear()
       +            self.payment_request = None
       +            return
       +        self.payto_e.is_pr = True
       +        if not pr.has_expired():
       +            self.payto_e.setGreen()
       +        else:
       +            self.payto_e.setExpired()
       +        self.payto_e.setText(pr.get_requestor())
       +        self.amount_e.setText(format_satoshis_plain(pr.get_amount(), self.decimal_point))
       +        self.message_e.setText(pr.get_memo())
       +        # signal to set fee
       +        self.amount_e.textEdited.emit("")
       +
       +    def payment_request_error(self):
       +        self.show_message(self.payment_request.error)
       +        self.payment_request = None
       +        self.do_clear()
       +
       +    def on_pr(self, request):
       +        self.payment_request = request
       +        if self.payment_request.verify(self.contacts):
       +            self.payment_request_ok_signal.emit()
       +        else:
       +            self.payment_request_error_signal.emit()
       +
       +    def pay_to_URI(self, URI):
       +        if not URI:
       +            return
       +        try:
       +            out = util.parse_URI(URI, self.on_pr)
       +        except BaseException as e:
       +            self.show_error(_('Invalid bitcoin URI:') + '\n' + str(e))
       +            return
       +        self.show_send_tab()
       +        r = out.get('r')
       +        sig = out.get('sig')
       +        name = out.get('name')
       +        if r or (name and sig):
       +            self.prepare_for_payment_request()
       +            return
       +        address = out.get('address')
       +        amount = out.get('amount')
       +        label = out.get('label')
       +        message = out.get('message')
       +        # use label as description (not BIP21 compliant)
       +        if label and not message:
       +            message = label
       +        if address:
       +            self.payto_e.setText(address)
       +        if message:
       +            self.message_e.setText(message)
       +        if amount:
       +            self.amount_e.setAmount(amount)
       +            self.amount_e.textEdited.emit("")
       +
       +
       +    def do_clear(self):
       +        self.is_max = False
       +        self.not_enough_funds = False
       +        self.payment_request = None
       +        self.payto_e.is_pr = False
       +        for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e,
       +                  self.fee_e, self.feerate_e]:
       +            e.setText('')
       +            e.setFrozen(False)
       +        self.fee_slider.activate()
       +        self.feerate_e.setAmount(self.config.fee_per_byte())
       +        self.size_e.setAmount(0)
       +        self.feerounding_icon.setVisible(False)
       +        self.set_pay_from([])
       +        self.tx_external_keypairs = {}
       +        self.update_status()
       +        run_hook('do_clear', self)
       +
       +    def set_frozen_state(self, addrs, freeze):
       +        self.wallet.set_frozen_state(addrs, freeze)
       +        self.address_list.update()
       +        self.utxo_list.update()
       +        self.update_fee()
       +
       +    def create_list_tab(self, l, toolbar=None):
       +        w = QWidget()
       +        w.searchable_list = l
       +        vbox = QVBoxLayout()
       +        w.setLayout(vbox)
       +        vbox.setContentsMargins(0, 0, 0, 0)
       +        vbox.setSpacing(0)
       +        if toolbar:
       +            vbox.addLayout(toolbar)
       +        vbox.addWidget(l)
       +        return w
       +
       +    def create_addresses_tab(self):
       +        from .address_list import AddressList
       +        self.address_list = l = AddressList(self)
       +        toolbar = l.create_toolbar(self.config)
       +        toolbar_shown = self.config.get('show_toolbar_addresses', False)
       +        l.show_toolbar(toolbar_shown)
       +        return self.create_list_tab(l, toolbar)
       +
       +    def create_utxo_tab(self):
       +        from .utxo_list import UTXOList
       +        self.utxo_list = l = UTXOList(self)
       +        return self.create_list_tab(l)
       +
       +    def create_contacts_tab(self):
       +        from .contact_list import ContactList
       +        self.contact_list = l = ContactList(self)
       +        return self.create_list_tab(l)
       +
       +    def remove_address(self, addr):
       +        if self.question(_("Do you want to remove {} from your wallet?").format(addr)):
       +            self.wallet.delete_address(addr)
       +            self.need_update.set()  # history, addresses, coins
       +            self.clear_receive_tab()
       +
       +    def get_coins(self):
       +        if self.pay_from:
       +            return self.pay_from
       +        else:
       +            return self.wallet.get_spendable_coins(None, self.config)
       +
       +    def spend_coins(self, coins):
       +        self.set_pay_from(coins)
       +        self.show_send_tab()
       +        self.update_fee()
       +
       +    def paytomany(self):
       +        self.show_send_tab()
       +        self.payto_e.paytomany()
       +        msg = '\n'.join([
       +            _('Enter a list of outputs in the \'Pay to\' field.'),
       +            _('One output per line.'),
       +            _('Format: address, amount'),
       +            _('You may load a CSV file using the file icon.')
       +        ])
       +        self.show_message(msg, title=_('Pay to many'))
       +
       +    def payto_contacts(self, labels):
       +        paytos = [self.get_contact_payto(label) for label in labels]
       +        self.show_send_tab()
       +        if len(paytos) == 1:
       +            self.payto_e.setText(paytos[0])
       +            self.amount_e.setFocus()
       +        else:
       +            text = "\n".join([payto + ", 0" for payto in paytos])
       +            self.payto_e.setText(text)
       +            self.payto_e.setFocus()
       +
       +    def set_contact(self, label, address):
       +        if not is_address(address):
       +            self.show_error(_('Invalid Address'))
       +            self.contact_list.update()  # Displays original unchanged value
       +            return False
       +        self.contacts[address] = ('address', label)
       +        self.contact_list.update()
       +        self.history_list.update()
       +        self.update_completions()
       +        return True
       +
       +    def delete_contacts(self, labels):
       +        if not self.question(_("Remove {} from your list of contacts?")
       +                             .format(" + ".join(labels))):
       +            return
       +        for label in labels:
       +            self.contacts.pop(label)
       +        self.history_list.update()
       +        self.contact_list.update()
       +        self.update_completions()
       +
       +    def show_invoice(self, key):
       +        pr = self.invoices.get(key)
       +        if pr is None:
       +            self.show_error('Cannot find payment request in wallet.')
       +            return
       +        pr.verify(self.contacts)
       +        self.show_pr_details(pr)
       +
       +    def show_pr_details(self, pr):
       +        key = pr.get_id()
       +        d = WindowModalDialog(self, _("Invoice"))
       +        vbox = QVBoxLayout(d)
       +        grid = QGridLayout()
       +        grid.addWidget(QLabel(_("Requestor") + ':'), 0, 0)
       +        grid.addWidget(QLabel(pr.get_requestor()), 0, 1)
       +        grid.addWidget(QLabel(_("Amount") + ':'), 1, 0)
       +        outputs_str = '\n'.join(map(lambda x: self.format_amount(x[2])+ self.base_unit() + ' @ ' + x[1], pr.get_outputs()))
       +        grid.addWidget(QLabel(outputs_str), 1, 1)
       +        expires = pr.get_expiration_date()
       +        grid.addWidget(QLabel(_("Memo") + ':'), 2, 0)
       +        grid.addWidget(QLabel(pr.get_memo()), 2, 1)
       +        grid.addWidget(QLabel(_("Signature") + ':'), 3, 0)
       +        grid.addWidget(QLabel(pr.get_verify_status()), 3, 1)
       +        if expires:
       +            grid.addWidget(QLabel(_("Expires") + ':'), 4, 0)
       +            grid.addWidget(QLabel(format_time(expires)), 4, 1)
       +        vbox.addLayout(grid)
       +        def do_export():
       +            fn = self.getSaveFileName(_("Save invoice to file"), "*.bip70")
       +            if not fn:
       +                return
       +            with open(fn, 'wb') as f:
       +                data = f.write(pr.raw)
       +            self.show_message(_('Invoice saved as' + ' ' + fn))
       +        exportButton = EnterButton(_('Save'), do_export)
       +        def do_delete():
       +            if self.question(_('Delete invoice?')):
       +                self.invoices.remove(key)
       +                self.history_list.update()
       +                self.invoice_list.update()
       +                d.close()
       +        deleteButton = EnterButton(_('Delete'), do_delete)
       +        vbox.addLayout(Buttons(exportButton, deleteButton, CloseButton(d)))
       +        d.exec_()
       +
       +    def do_pay_invoice(self, key):
       +        pr = self.invoices.get(key)
       +        self.payment_request = pr
       +        self.prepare_for_payment_request()
       +        pr.error = None  # this forces verify() to re-run
       +        if pr.verify(self.contacts):
       +            self.payment_request_ok()
       +        else:
       +            self.payment_request_error()
       +
       +    def create_console_tab(self):
       +        from .console import Console
       +        self.console = console = Console()
       +        return console
       +
       +    def update_console(self):
       +        console = self.console
       +        console.history = self.config.get("console-history",[])
       +        console.history_index = len(console.history)
       +
       +        console.updateNamespace({'wallet' : self.wallet,
       +                                 'network' : self.network,
       +                                 'plugins' : self.gui_object.plugins,
       +                                 'window': self})
       +        console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
       +
       +        c = commands.Commands(self.config, self.wallet, self.network, lambda: self.console.set_json(True))
       +        methods = {}
       +        def mkfunc(f, method):
       +            return lambda *args: f(method, args, self.password_dialog)
       +        for m in dir(c):
       +            if m[0]=='_' or m in ['network','wallet']: continue
       +            methods[m] = mkfunc(c._run, m)
       +
       +        console.updateNamespace(methods)
       +
       +    def create_status_bar(self):
       +
       +        sb = QStatusBar()
       +        sb.setFixedHeight(35)
       +        qtVersion = qVersion()
       +
       +        self.balance_label = QLabel("")
       +        self.balance_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
       +        self.balance_label.setStyleSheet("""QLabel { padding: 0 }""")
       +        sb.addWidget(self.balance_label)
       +
       +        self.search_box = QLineEdit()
       +        self.search_box.textChanged.connect(self.do_search)
       +        self.search_box.hide()
       +        sb.addPermanentWidget(self.search_box)
       +
       +        self.lock_icon = QIcon()
       +        self.password_button = StatusBarButton(self.lock_icon, _("Password"), self.change_password_dialog )
       +        sb.addPermanentWidget(self.password_button)
       +
       +        sb.addPermanentWidget(StatusBarButton(QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
       +        self.seed_button = StatusBarButton(QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
       +        sb.addPermanentWidget(self.seed_button)
       +        self.status_button = StatusBarButton(QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self))
       +        sb.addPermanentWidget(self.status_button)
       +        run_hook('create_status_bar', sb)
       +        self.setStatusBar(sb)
       +
       +    def update_lock_icon(self):
       +        icon = QIcon(":icons/lock.png") if self.wallet.has_password() else QIcon(":icons/unlock.png")
       +        self.password_button.setIcon(icon)
       +
       +    def update_buttons_on_seed(self):
       +        self.seed_button.setVisible(self.wallet.has_seed())
       +        self.password_button.setVisible(self.wallet.may_have_password())
       +        self.send_button.setVisible(not self.wallet.is_watching_only())
       +
       +    def change_password_dialog(self):
       +        from electrum.storage import STO_EV_XPUB_PW
       +        if self.wallet.get_available_storage_encryption_version() == STO_EV_XPUB_PW:
       +            from .password_dialog import ChangePasswordDialogForHW
       +            d = ChangePasswordDialogForHW(self, self.wallet)
       +            ok, encrypt_file = d.run()
       +            if not ok:
       +                return
       +
       +            try:
       +                hw_dev_pw = self.wallet.keystore.get_password_for_storage_encryption()
       +            except UserCancelled:
       +                return
       +            except BaseException as e:
       +                traceback.print_exc(file=sys.stderr)
       +                self.show_error(str(e))
       +                return
       +            old_password = hw_dev_pw if self.wallet.has_password() else None
       +            new_password = hw_dev_pw if encrypt_file else None
       +        else:
       +            from .password_dialog import ChangePasswordDialogForSW
       +            d = ChangePasswordDialogForSW(self, self.wallet)
       +            ok, old_password, new_password, encrypt_file = d.run()
       +
       +        if not ok:
       +            return
       +        try:
       +            self.wallet.update_password(old_password, new_password, encrypt_file)
       +        except InvalidPassword as e:
       +            self.show_error(str(e))
       +            return
       +        except BaseException:
       +            traceback.print_exc(file=sys.stdout)
       +            self.show_error(_('Failed to update password'))
       +            return
       +        msg = _('Password was updated successfully') if self.wallet.has_password() else _('Password is disabled, this wallet is not protected')
       +        self.show_message(msg, title=_("Success"))
       +        self.update_lock_icon()
       +
       +    def toggle_search(self):
       +        tab = self.tabs.currentWidget()
       +        #if hasattr(tab, 'searchable_list'):
       +        #    tab.searchable_list.toggle_toolbar()
       +        #return
       +        self.search_box.setHidden(not self.search_box.isHidden())
       +        if not self.search_box.isHidden():
       +            self.search_box.setFocus(1)
       +        else:
       +            self.do_search('')
       +
       +    def do_search(self, t):
       +        tab = self.tabs.currentWidget()
       +        if hasattr(tab, 'searchable_list'):
       +            tab.searchable_list.filter(t)
       +
       +    def new_contact_dialog(self):
       +        d = WindowModalDialog(self, _("New Contact"))
       +        vbox = QVBoxLayout(d)
       +        vbox.addWidget(QLabel(_('New Contact') + ':'))
       +        grid = QGridLayout()
       +        line1 = QLineEdit()
       +        line1.setFixedWidth(280)
       +        line2 = QLineEdit()
       +        line2.setFixedWidth(280)
       +        grid.addWidget(QLabel(_("Address")), 1, 0)
       +        grid.addWidget(line1, 1, 1)
       +        grid.addWidget(QLabel(_("Name")), 2, 0)
       +        grid.addWidget(line2, 2, 1)
       +        vbox.addLayout(grid)
       +        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
       +        if d.exec_():
       +            self.set_contact(line2.text(), line1.text())
       +
       +    def show_master_public_keys(self):
       +        dialog = WindowModalDialog(self, _("Wallet Information"))
       +        dialog.setMinimumSize(500, 100)
       +        mpk_list = self.wallet.get_master_public_keys()
       +        vbox = QVBoxLayout()
       +        wallet_type = self.wallet.storage.get('wallet_type', '')
       +        grid = QGridLayout()
       +        basename = os.path.basename(self.wallet.storage.path)
       +        grid.addWidget(QLabel(_("Wallet name")+ ':'), 0, 0)
       +        grid.addWidget(QLabel(basename), 0, 1)
       +        grid.addWidget(QLabel(_("Wallet type")+ ':'), 1, 0)
       +        grid.addWidget(QLabel(wallet_type), 1, 1)
       +        grid.addWidget(QLabel(_("Script type")+ ':'), 2, 0)
       +        grid.addWidget(QLabel(self.wallet.txin_type), 2, 1)
       +        vbox.addLayout(grid)
       +        if self.wallet.is_deterministic():
       +            mpk_text = ShowQRTextEdit()
       +            mpk_text.setMaximumHeight(150)
       +            mpk_text.addCopyButton(self.app)
       +            def show_mpk(index):
       +                mpk_text.setText(mpk_list[index])
       +            # only show the combobox in case multiple accounts are available
       +            if len(mpk_list) > 1:
       +                def label(key):
       +                    if isinstance(self.wallet, Multisig_Wallet):
       +                        return _("cosigner") + ' ' + str(key+1)
       +                    return ''
       +                labels = [label(i) for i in range(len(mpk_list))]
       +                on_click = lambda clayout: show_mpk(clayout.selected_index())
       +                labels_clayout = ChoicesLayout(_("Master Public Keys"), labels, on_click)
       +                vbox.addLayout(labels_clayout.layout())
       +            else:
       +                vbox.addWidget(QLabel(_("Master Public Key")))
       +            show_mpk(0)
       +            vbox.addWidget(mpk_text)
       +        vbox.addStretch(1)
       +        vbox.addLayout(Buttons(CloseButton(dialog)))
       +        dialog.setLayout(vbox)
       +        dialog.exec_()
       +
       +    def remove_wallet(self):
       +        if self.question('\n'.join([
       +                _('Delete wallet file?'),
       +                "%s"%self.wallet.storage.path,
       +                _('If your wallet contains funds, make sure you have saved its seed.')])):
       +            self._delete_wallet()
       +
       +    @protected
       +    def _delete_wallet(self, password):
       +        wallet_path = self.wallet.storage.path
       +        basename = os.path.basename(wallet_path)
       +        self.gui_object.daemon.stop_wallet(wallet_path)
       +        self.close()
       +        os.unlink(wallet_path)
       +        self.show_error(_("Wallet removed: {}").format(basename))
       +
       +    @protected
       +    def show_seed_dialog(self, password):
       +        if not self.wallet.has_seed():
       +            self.show_message(_('This wallet has no seed'))
       +            return
       +        keystore = self.wallet.get_keystore()
       +        try:
       +            seed = keystore.get_seed(password)
       +            passphrase = keystore.get_passphrase(password)
       +        except BaseException as e:
       +            self.show_error(str(e))
       +            return
       +        from .seed_dialog import SeedDialog
       +        d = SeedDialog(self, seed, passphrase)
       +        d.exec_()
       +
       +    def show_qrcode(self, data, title = _("QR code"), parent=None):
       +        if not data:
       +            return
       +        d = QRDialog(data, parent or self, title)
       +        d.exec_()
       +
       +    @protected
       +    def show_private_key(self, address, password):
       +        if not address:
       +            return
       +        try:
       +            pk, redeem_script = self.wallet.export_private_key(address, password)
       +        except Exception as e:
       +            traceback.print_exc(file=sys.stdout)
       +            self.show_message(str(e))
       +            return
       +        xtype = bitcoin.deserialize_privkey(pk)[0]
       +        d = WindowModalDialog(self, _("Private key"))
       +        d.setMinimumSize(600, 150)
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(QLabel(_("Address") + ': ' + address))
       +        vbox.addWidget(QLabel(_("Script type") + ': ' + xtype))
       +        vbox.addWidget(QLabel(_("Private key") + ':'))
       +        keys_e = ShowQRTextEdit(text=pk)
       +        keys_e.addCopyButton(self.app)
       +        vbox.addWidget(keys_e)
       +        if redeem_script:
       +            vbox.addWidget(QLabel(_("Redeem Script") + ':'))
       +            rds_e = ShowQRTextEdit(text=redeem_script)
       +            rds_e.addCopyButton(self.app)
       +            vbox.addWidget(rds_e)
       +        vbox.addLayout(Buttons(CloseButton(d)))
       +        d.setLayout(vbox)
       +        d.exec_()
       +
       +    msg_sign = _("Signing with an address actually means signing with the corresponding "
       +                "private key, and verifying with the corresponding public key. The "
       +                "address you have entered does not have a unique public key, so these "
       +                "operations cannot be performed.") + '\n\n' + \
       +               _('The operation is undefined. Not just in Electrum, but in general.')
       +
       +    @protected
       +    def do_sign(self, address, message, signature, password):
       +        address  = address.text().strip()
       +        message = message.toPlainText().strip()
       +        if not bitcoin.is_address(address):
       +            self.show_message(_('Invalid Bitcoin address.'))
       +            return
       +        if self.wallet.is_watching_only():
       +            self.show_message(_('This is a watching-only wallet.'))
       +            return
       +        if not self.wallet.is_mine(address):
       +            self.show_message(_('Address not in wallet.'))
       +            return
       +        txin_type = self.wallet.get_txin_type(address)
       +        if txin_type not in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
       +            self.show_message(_('Cannot sign messages with this type of address:') + \
       +                              ' ' + txin_type + '\n\n' + self.msg_sign)
       +            return
       +        task = partial(self.wallet.sign_message, address, message, password)
       +
       +        def show_signed_message(sig):
       +            try:
       +                signature.setText(base64.b64encode(sig).decode('ascii'))
       +            except RuntimeError:
       +                # (signature) wrapped C/C++ object has been deleted
       +                pass
       +
       +        self.wallet.thread.add(task, on_success=show_signed_message)
       +
       +    def do_verify(self, address, message, signature):
       +        address  = address.text().strip()
       +        message = message.toPlainText().strip().encode('utf-8')
       +        if not bitcoin.is_address(address):
       +            self.show_message(_('Invalid Bitcoin address.'))
       +            return
       +        try:
       +            # This can throw on invalid base64
       +            sig = base64.b64decode(str(signature.toPlainText()))
       +            verified = ecc.verify_message_with_address(address, sig, message)
       +        except Exception as e:
       +            verified = False
       +        if verified:
       +            self.show_message(_("Signature verified"))
       +        else:
       +            self.show_error(_("Wrong signature"))
       +
       +    def sign_verify_message(self, address=''):
       +        d = WindowModalDialog(self, _('Sign/verify Message'))
       +        d.setMinimumSize(610, 290)
       +
       +        layout = QGridLayout(d)
       +
       +        message_e = QTextEdit()
       +        layout.addWidget(QLabel(_('Message')), 1, 0)
       +        layout.addWidget(message_e, 1, 1)
       +        layout.setRowStretch(2,3)
       +
       +        address_e = QLineEdit()
       +        address_e.setText(address)
       +        layout.addWidget(QLabel(_('Address')), 2, 0)
       +        layout.addWidget(address_e, 2, 1)
       +
       +        signature_e = QTextEdit()
       +        layout.addWidget(QLabel(_('Signature')), 3, 0)
       +        layout.addWidget(signature_e, 3, 1)
       +        layout.setRowStretch(3,1)
       +
       +        hbox = QHBoxLayout()
       +
       +        b = QPushButton(_("Sign"))
       +        b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
       +        hbox.addWidget(b)
       +
       +        b = QPushButton(_("Verify"))
       +        b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
       +        hbox.addWidget(b)
       +
       +        b = QPushButton(_("Close"))
       +        b.clicked.connect(d.accept)
       +        hbox.addWidget(b)
       +        layout.addLayout(hbox, 4, 1)
       +        d.exec_()
       +
       +    @protected
       +    def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
       +        if self.wallet.is_watching_only():
       +            self.show_message(_('This is a watching-only wallet.'))
       +            return
       +        cyphertext = encrypted_e.toPlainText()
       +        task = partial(self.wallet.decrypt_message, pubkey_e.text(), cyphertext, password)
       +
       +        def setText(text):
       +            try:
       +                message_e.setText(text.decode('utf-8'))
       +            except RuntimeError:
       +                # (message_e) wrapped C/C++ object has been deleted
       +                pass
       +
       +        self.wallet.thread.add(task, on_success=setText)
       +
       +    def do_encrypt(self, message_e, pubkey_e, encrypted_e):
       +        message = message_e.toPlainText()
       +        message = message.encode('utf-8')
       +        try:
       +            public_key = ecc.ECPubkey(bfh(pubkey_e.text()))
       +        except BaseException as e:
       +            traceback.print_exc(file=sys.stdout)            
       +            self.show_warning(_('Invalid Public key')) 
       +            return
       +        encrypted = public_key.encrypt_message(message)
       +        encrypted_e.setText(encrypted.decode('ascii'))
       +
       +    def encrypt_message(self, address=''):
       +        d = WindowModalDialog(self, _('Encrypt/decrypt Message'))
       +        d.setMinimumSize(610, 490)
       +
       +        layout = QGridLayout(d)
       +
       +        message_e = QTextEdit()
       +        layout.addWidget(QLabel(_('Message')), 1, 0)
       +        layout.addWidget(message_e, 1, 1)
       +        layout.setRowStretch(2,3)
       +
       +        pubkey_e = QLineEdit()
       +        if address:
       +            pubkey = self.wallet.get_public_key(address)
       +            pubkey_e.setText(pubkey)
       +        layout.addWidget(QLabel(_('Public key')), 2, 0)
       +        layout.addWidget(pubkey_e, 2, 1)
       +
       +        encrypted_e = QTextEdit()
       +        layout.addWidget(QLabel(_('Encrypted')), 3, 0)
       +        layout.addWidget(encrypted_e, 3, 1)
       +        layout.setRowStretch(3,1)
       +
       +        hbox = QHBoxLayout()
       +        b = QPushButton(_("Encrypt"))
       +        b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
       +        hbox.addWidget(b)
       +
       +        b = QPushButton(_("Decrypt"))
       +        b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
       +        hbox.addWidget(b)
       +
       +        b = QPushButton(_("Close"))
       +        b.clicked.connect(d.accept)
       +        hbox.addWidget(b)
       +
       +        layout.addLayout(hbox, 4, 1)
       +        d.exec_()
       +
       +    def password_dialog(self, msg=None, parent=None):
       +        from .password_dialog import PasswordDialog
       +        parent = parent or self
       +        d = PasswordDialog(parent, msg)
       +        return d.run()
       +
       +    def tx_from_text(self, txt):
       +        from electrum.transaction import tx_from_str
       +        try:
       +            tx = tx_from_str(txt)
       +            return Transaction(tx)
       +        except BaseException as e:
       +            self.show_critical(_("Electrum was unable to parse your transaction") + ":\n" + str(e))
       +            return
       +
       +    def read_tx_from_qrcode(self):
       +        from electrum import qrscanner
       +        try:
       +            data = qrscanner.scan_barcode(self.config.get_video_device())
       +        except BaseException as e:
       +            self.show_error(str(e))
       +            return
       +        if not data:
       +            return
       +        # if the user scanned a bitcoin URI
       +        if str(data).startswith("bitcoin:"):
       +            self.pay_to_URI(data)
       +            return
       +        # else if the user scanned an offline signed tx
       +        try:
       +            data = bh2u(bitcoin.base_decode(data, length=None, base=43))
       +        except BaseException as e:
       +            self.show_error((_('Could not decode QR code')+':\n{}').format(e))
       +            return
       +        tx = self.tx_from_text(data)
       +        if not tx:
       +            return
       +        self.show_transaction(tx)
       +
       +    def read_tx_from_file(self):
       +        fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
       +        if not fileName:
       +            return
       +        try:
       +            with open(fileName, "r") as f:
       +                file_content = f.read()
       +        except (ValueError, IOError, os.error) as reason:
       +            self.show_critical(_("Electrum was unable to open your transaction file") + "\n" + str(reason), title=_("Unable to read file or no transaction found"))
       +            return
       +        return self.tx_from_text(file_content)
       +
       +    def do_process_from_text(self):
       +        text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
       +        if not text:
       +            return
       +        tx = self.tx_from_text(text)
       +        if tx:
       +            self.show_transaction(tx)
       +
       +    def do_process_from_file(self):
       +        tx = self.read_tx_from_file()
       +        if tx:
       +            self.show_transaction(tx)
       +
       +    def do_process_from_txid(self):
       +        from electrum import transaction
       +        txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
       +        if ok and txid:
       +            txid = str(txid).strip()
       +            try:
       +                r = self.network.get_transaction(txid)
       +            except BaseException as e:
       +                self.show_message(str(e))
       +                return
       +            tx = transaction.Transaction(r)
       +            self.show_transaction(tx)
       +
       +    @protected
       +    def export_privkeys_dialog(self, password):
       +        if self.wallet.is_watching_only():
       +            self.show_message(_("This is a watching-only wallet"))
       +            return
       +
       +        if isinstance(self.wallet, Multisig_Wallet):
       +            self.show_message(_('WARNING: This is a multi-signature wallet.') + '\n' +
       +                              _('It cannot be "backed up" by simply exporting these private keys.'))
       +
       +        d = WindowModalDialog(self, _('Private keys'))
       +        d.setMinimumSize(980, 300)
       +        vbox = QVBoxLayout(d)
       +
       +        msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
       +                              _("Exposing a single private key can compromise your entire wallet!"),
       +                              _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
       +        vbox.addWidget(QLabel(msg))
       +
       +        e = QTextEdit()
       +        e.setReadOnly(True)
       +        vbox.addWidget(e)
       +
       +        defaultname = 'electrum-private-keys.csv'
       +        select_msg = _('Select file to export your private keys to')
       +        hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
       +        vbox.addLayout(hbox)
       +
       +        b = OkButton(d, _('Export'))
       +        b.setEnabled(False)
       +        vbox.addLayout(Buttons(CancelButton(d), b))
       +
       +        private_keys = {}
       +        addresses = self.wallet.get_addresses()
       +        done = False
       +        cancelled = False
       +        def privkeys_thread():
       +            for addr in addresses:
       +                time.sleep(0.1)
       +                if done or cancelled:
       +                    break
       +                privkey = self.wallet.export_private_key(addr, password)[0]
       +                private_keys[addr] = privkey
       +                self.computing_privkeys_signal.emit()
       +            if not cancelled:
       +                self.computing_privkeys_signal.disconnect()
       +                self.show_privkeys_signal.emit()
       +
       +        def show_privkeys():
       +            s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
       +            e.setText(s)
       +            b.setEnabled(True)
       +            self.show_privkeys_signal.disconnect()
       +            nonlocal done
       +            done = True
       +
       +        def on_dialog_closed(*args):
       +            nonlocal done
       +            nonlocal cancelled
       +            if not done:
       +                cancelled = True
       +                self.computing_privkeys_signal.disconnect()
       +                self.show_privkeys_signal.disconnect()
       +
       +        self.computing_privkeys_signal.connect(lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
       +        self.show_privkeys_signal.connect(show_privkeys)
       +        d.finished.connect(on_dialog_closed)
       +        threading.Thread(target=privkeys_thread).start()
       +
       +        if not d.exec_():
       +            done = True
       +            return
       +
       +        filename = filename_e.text()
       +        if not filename:
       +            return
       +
       +        try:
       +            self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
       +        except (IOError, os.error) as reason:
       +            txt = "\n".join([
       +                _("Electrum was unable to produce a private key-export."),
       +                str(reason)
       +            ])
       +            self.show_critical(txt, title=_("Unable to create csv"))
       +
       +        except Exception as e:
       +            self.show_message(str(e))
       +            return
       +
       +        self.show_message(_("Private keys exported."))
       +
       +    def do_export_privkeys(self, fileName, pklist, is_csv):
       +        with open(fileName, "w+") as f:
       +            if is_csv:
       +                transaction = csv.writer(f)
       +                transaction.writerow(["address", "private_key"])
       +                for addr, pk in pklist.items():
       +                    transaction.writerow(["%34s"%addr,pk])
       +            else:
       +                import json
       +                f.write(json.dumps(pklist, indent = 4))
       +
       +    def do_import_labels(self):
       +        def import_labels(path):
       +            def _validate(data):
       +                return data  # TODO
       +
       +            def import_labels_assign(data):
       +                for key, value in data.items():
       +                    self.wallet.set_label(key, value)
       +            import_meta(path, _validate, import_labels_assign)
       +
       +        def on_import():
       +            self.need_update.set()
       +        import_meta_gui(self, _('labels'), import_labels, on_import)
       +
       +    def do_export_labels(self):
       +        def export_labels(filename):
       +            export_meta(self.wallet.labels, filename)
       +        export_meta_gui(self, _('labels'), export_labels)
       +
       +    def sweep_key_dialog(self):
       +        d = WindowModalDialog(self, title=_('Sweep private keys'))
       +        d.setMinimumSize(600, 300)
       +
       +        vbox = QVBoxLayout(d)
       +
       +        hbox_top = QHBoxLayout()
       +        hbox_top.addWidget(QLabel(_("Enter private keys:")))
       +        hbox_top.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
       +        vbox.addLayout(hbox_top)
       +
       +        keys_e = ScanQRTextEdit(allow_multi=True)
       +        keys_e.setTabChangesFocus(True)
       +        vbox.addWidget(keys_e)
       +
       +        addresses = self.wallet.get_unused_addresses()
       +        if not addresses:
       +            try:
       +                addresses = self.wallet.get_receiving_addresses()
       +            except AttributeError:
       +                addresses = self.wallet.get_addresses()
       +        h, address_e = address_field(addresses)
       +        vbox.addLayout(h)
       +
       +        vbox.addStretch(1)
       +        button = OkButton(d, _('Sweep'))
       +        vbox.addLayout(Buttons(CancelButton(d), button))
       +        button.setEnabled(False)
       +
       +        def get_address():
       +            addr = str(address_e.text()).strip()
       +            if bitcoin.is_address(addr):
       +                return addr
       +
       +        def get_pk():
       +            text = str(keys_e.toPlainText())
       +            return keystore.get_private_keys(text)
       +
       +        f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
       +        on_address = lambda text: address_e.setStyleSheet((ColorScheme.DEFAULT if get_address() else ColorScheme.RED).as_stylesheet())
       +        keys_e.textChanged.connect(f)
       +        address_e.textChanged.connect(f)
       +        address_e.textChanged.connect(on_address)
       +        if not d.exec_():
       +            return
       +        from electrum.wallet import sweep_preparations
       +        try:
       +            self.do_clear()
       +            coins, keypairs = sweep_preparations(get_pk(), self.network)
       +            self.tx_external_keypairs = keypairs
       +            self.spend_coins(coins)
       +            self.payto_e.setText(get_address())
       +            self.spend_max()
       +            self.payto_e.setFrozen(True)
       +            self.amount_e.setFrozen(True)
       +        except BaseException as e:
       +            self.show_message(str(e))
       +            return
       +        self.warn_if_watching_only()
       +
       +    def _do_import(self, title, header_layout, func):
       +        text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True)
       +        if not text:
       +            return
       +        bad = []
       +        good = []
       +        for key in str(text).split():
       +            try:
       +                addr = func(key)
       +                good.append(addr)
       +            except BaseException as e:
       +                bad.append(key)
       +                continue
       +        if good:
       +            self.show_message(_("The following addresses were added") + ':\n' + '\n'.join(good))
       +        if bad:
       +            self.show_critical(_("The following inputs could not be imported") + ':\n'+ '\n'.join(bad))
       +        self.address_list.update()
       +        self.history_list.update()
       +
       +    def import_addresses(self):
       +        if not self.wallet.can_import_address():
       +            return
       +        title, msg = _('Import addresses'), _("Enter addresses")+':'
       +        self._do_import(title, msg, self.wallet.import_address)
       +
       +    @protected
       +    def do_import_privkey(self, password):
       +        if not self.wallet.can_import_privkey():
       +            return
       +        title = _('Import private keys')
       +        header_layout = QHBoxLayout()
       +        header_layout.addWidget(QLabel(_("Enter private keys")+':'))
       +        header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
       +        self._do_import(title, header_layout, lambda x: self.wallet.import_private_key(x, password))
       +
       +    def update_fiat(self):
       +        b = self.fx and self.fx.is_enabled()
       +        self.fiat_send_e.setVisible(b)
       +        self.fiat_receive_e.setVisible(b)
       +        self.history_list.refresh_headers()
       +        self.history_list.update()
       +        self.address_list.refresh_headers()
       +        self.address_list.update()
       +        self.update_status()
       +
       +    def settings_dialog(self):
       +        self.need_restart = False
       +        d = WindowModalDialog(self, _('Preferences'))
       +        vbox = QVBoxLayout()
       +        tabs = QTabWidget()
       +        gui_widgets = []
       +        fee_widgets = []
       +        tx_widgets = []
       +        id_widgets = []
       +
       +        # language
       +        lang_help = _('Select which language is used in the GUI (after restart).')
       +        lang_label = HelpLabel(_('Language') + ':', lang_help)
       +        lang_combo = QComboBox()
       +        from electrum.i18n import languages
       +        lang_combo.addItems(list(languages.values()))
       +        lang_keys = list(languages.keys())
       +        lang_cur_setting = self.config.get("language", '')
       +        try:
       +            index = lang_keys.index(lang_cur_setting)
       +        except ValueError:  # not in list
       +            index = 0
       +        lang_combo.setCurrentIndex(index)
       +        if not self.config.is_modifiable('language'):
       +            for w in [lang_combo, lang_label]: w.setEnabled(False)
       +        def on_lang(x):
       +            lang_request = list(languages.keys())[lang_combo.currentIndex()]
       +            if lang_request != self.config.get('language'):
       +                self.config.set_key("language", lang_request, True)
       +                self.need_restart = True
       +        lang_combo.currentIndexChanged.connect(on_lang)
       +        gui_widgets.append((lang_label, lang_combo))
       +
       +        nz_help = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
       +        nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help)
       +        nz = QSpinBox()
       +        nz.setMinimum(0)
       +        nz.setMaximum(self.decimal_point)
       +        nz.setValue(self.num_zeros)
       +        if not self.config.is_modifiable('num_zeros'):
       +            for w in [nz, nz_label]: w.setEnabled(False)
       +        def on_nz():
       +            value = nz.value()
       +            if self.num_zeros != value:
       +                self.num_zeros = value
       +                self.config.set_key('num_zeros', value, True)
       +                self.history_list.update()
       +                self.address_list.update()
       +        nz.valueChanged.connect(on_nz)
       +        gui_widgets.append((nz_label, nz))
       +
       +        msg = '\n'.join([
       +            _('Time based: fee rate is based on average confirmation time estimates'),
       +            _('Mempool based: fee rate is targeting a depth in the memory pool')
       +            ]
       +        )
       +        fee_type_label = HelpLabel(_('Fee estimation') + ':', msg)
       +        fee_type_combo = QComboBox()
       +        fee_type_combo.addItems([_('Static'), _('ETA'), _('Mempool')])
       +        fee_type_combo.setCurrentIndex((2 if self.config.use_mempool_fees() else 1) if self.config.is_dynfee() else 0)
       +        def on_fee_type(x):
       +            self.config.set_key('mempool_fees', x==2)
       +            self.config.set_key('dynamic_fees', x>0)
       +            self.fee_slider.update()
       +        fee_type_combo.currentIndexChanged.connect(on_fee_type)
       +        fee_widgets.append((fee_type_label, fee_type_combo))
       +
       +        feebox_cb = QCheckBox(_('Edit fees manually'))
       +        feebox_cb.setChecked(self.config.get('show_fee', False))
       +        feebox_cb.setToolTip(_("Show fee edit box in send tab."))
       +        def on_feebox(x):
       +            self.config.set_key('show_fee', x == Qt.Checked)
       +            self.fee_adv_controls.setVisible(bool(x))
       +        feebox_cb.stateChanged.connect(on_feebox)
       +        fee_widgets.append((feebox_cb, None))
       +
       +        use_rbf_cb = QCheckBox(_('Use Replace-By-Fee'))
       +        use_rbf_cb.setChecked(self.config.get('use_rbf', True))
       +        use_rbf_cb.setToolTip(
       +            _('If you check this box, your transactions will be marked as non-final,') + '\n' + \
       +            _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \
       +            _('Note that some merchants do not accept non-final transactions until they are confirmed.'))
       +        def on_use_rbf(x):
       +            self.config.set_key('use_rbf', x == Qt.Checked)
       +        use_rbf_cb.stateChanged.connect(on_use_rbf)
       +        fee_widgets.append((use_rbf_cb, None))
       +
       +        msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
       +              + _('The following alias providers are available:') + '\n'\
       +              + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
       +              + 'For more information, see https://openalias.org'
       +        alias_label = HelpLabel(_('OpenAlias') + ':', msg)
       +        alias = self.config.get('alias','')
       +        alias_e = QLineEdit(alias)
       +        def set_alias_color():
       +            if not self.config.get('alias'):
       +                alias_e.setStyleSheet("")
       +                return
       +            if self.alias_info:
       +                alias_addr, alias_name, validated = self.alias_info
       +                alias_e.setStyleSheet((ColorScheme.GREEN if validated else ColorScheme.RED).as_stylesheet(True))
       +            else:
       +                alias_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
       +        def on_alias_edit():
       +            alias_e.setStyleSheet("")
       +            alias = str(alias_e.text())
       +            self.config.set_key('alias', alias, True)
       +            if alias:
       +                self.fetch_alias()
       +        set_alias_color()
       +        self.alias_received_signal.connect(set_alias_color)
       +        alias_e.editingFinished.connect(on_alias_edit)
       +        id_widgets.append((alias_label, alias_e))
       +
       +        # SSL certificate
       +        msg = ' '.join([
       +            _('SSL certificate used to sign payment requests.'),
       +            _('Use setconfig to set ssl_chain and ssl_privkey.'),
       +        ])
       +        if self.config.get('ssl_privkey') or self.config.get('ssl_chain'):
       +            try:
       +                SSL_identity = paymentrequest.check_ssl_config(self.config)
       +                SSL_error = None
       +            except BaseException as e:
       +                SSL_identity = "error"
       +                SSL_error = str(e)
       +        else:
       +            SSL_identity = ""
       +            SSL_error = None
       +        SSL_id_label = HelpLabel(_('SSL certificate') + ':', msg)
       +        SSL_id_e = QLineEdit(SSL_identity)
       +        SSL_id_e.setStyleSheet((ColorScheme.RED if SSL_error else ColorScheme.GREEN).as_stylesheet(True) if SSL_identity else '')
       +        if SSL_error:
       +            SSL_id_e.setToolTip(SSL_error)
       +        SSL_id_e.setReadOnly(True)
       +        id_widgets.append((SSL_id_label, SSL_id_e))
       +
       +        units = base_units_list
       +        msg = (_('Base unit of your wallet.')
       +               + '\n1 BTC = 1000 mBTC. 1 mBTC = 1000 bits. 1 bit = 100 sat.\n'
       +               + _('This setting affects the Send tab, and all balance related fields.'))
       +        unit_label = HelpLabel(_('Base unit') + ':', msg)
       +        unit_combo = QComboBox()
       +        unit_combo.addItems(units)
       +        unit_combo.setCurrentIndex(units.index(self.base_unit()))
       +        def on_unit(x, nz):
       +            unit_result = units[unit_combo.currentIndex()]
       +            if self.base_unit() == unit_result:
       +                return
       +            edits = self.amount_e, self.fee_e, self.receive_amount_e
       +            amounts = [edit.get_amount() for edit in edits]
       +            self.decimal_point = base_unit_name_to_decimal_point(unit_result)
       +            self.config.set_key('decimal_point', self.decimal_point, True)
       +            nz.setMaximum(self.decimal_point)
       +            self.history_list.update()
       +            self.request_list.update()
       +            self.address_list.update()
       +            for edit, amount in zip(edits, amounts):
       +                edit.setAmount(amount)
       +            self.update_status()
       +        unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
       +        gui_widgets.append((unit_label, unit_combo))
       +
       +        block_explorers = sorted(util.block_explorer_info().keys())
       +        msg = _('Choose which online block explorer to use for functions that open a web browser')
       +        block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg)
       +        block_ex_combo = QComboBox()
       +        block_ex_combo.addItems(block_explorers)
       +        block_ex_combo.setCurrentIndex(block_ex_combo.findText(util.block_explorer(self.config)))
       +        def on_be(x):
       +            be_result = block_explorers[block_ex_combo.currentIndex()]
       +            self.config.set_key('block_explorer', be_result, True)
       +        block_ex_combo.currentIndexChanged.connect(on_be)
       +        gui_widgets.append((block_ex_label, block_ex_combo))
       +
       +        from electrum import qrscanner
       +        system_cameras = qrscanner._find_system_cameras()
       +        qr_combo = QComboBox()
       +        qr_combo.addItem("Default","default")
       +        for camera, device in system_cameras.items():
       +            qr_combo.addItem(camera, device)
       +        #combo.addItem("Manually specify a device", config.get("video_device"))
       +        index = qr_combo.findData(self.config.get("video_device"))
       +        qr_combo.setCurrentIndex(index)
       +        msg = _("Install the zbar package to enable this.")
       +        qr_label = HelpLabel(_('Video Device') + ':', msg)
       +        qr_combo.setEnabled(qrscanner.libzbar is not None)
       +        on_video_device = lambda x: self.config.set_key("video_device", qr_combo.itemData(x), True)
       +        qr_combo.currentIndexChanged.connect(on_video_device)
       +        gui_widgets.append((qr_label, qr_combo))
       +
       +        colortheme_combo = QComboBox()
       +        colortheme_combo.addItem(_('Light'), 'default')
       +        colortheme_combo.addItem(_('Dark'), 'dark')
       +        index = colortheme_combo.findData(self.config.get('qt_gui_color_theme', 'default'))
       +        colortheme_combo.setCurrentIndex(index)
       +        colortheme_label = QLabel(_('Color theme') + ':')
       +        def on_colortheme(x):
       +            self.config.set_key('qt_gui_color_theme', colortheme_combo.itemData(x), True)
       +            self.need_restart = True
       +        colortheme_combo.currentIndexChanged.connect(on_colortheme)
       +        gui_widgets.append((colortheme_label, colortheme_combo))
       +
       +        usechange_cb = QCheckBox(_('Use change addresses'))
       +        usechange_cb.setChecked(self.wallet.use_change)
       +        if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
       +        def on_usechange(x):
       +            usechange_result = x == Qt.Checked
       +            if self.wallet.use_change != usechange_result:
       +                self.wallet.use_change = usechange_result
       +                self.wallet.storage.put('use_change', self.wallet.use_change)
       +                multiple_cb.setEnabled(self.wallet.use_change)
       +        usechange_cb.stateChanged.connect(on_usechange)
       +        usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))
       +        tx_widgets.append((usechange_cb, None))
       +
       +        def on_multiple(x):
       +            multiple = x == Qt.Checked
       +            if self.wallet.multiple_change != multiple:
       +                self.wallet.multiple_change = multiple
       +                self.wallet.storage.put('multiple_change', multiple)
       +        multiple_change = self.wallet.multiple_change
       +        multiple_cb = QCheckBox(_('Use multiple change addresses'))
       +        multiple_cb.setEnabled(self.wallet.use_change)
       +        multiple_cb.setToolTip('\n'.join([
       +            _('In some cases, use up to 3 change addresses in order to break '
       +              'up large coin amounts and obfuscate the recipient address.'),
       +            _('This may result in higher transactions fees.')
       +        ]))
       +        multiple_cb.setChecked(multiple_change)
       +        multiple_cb.stateChanged.connect(on_multiple)
       +        tx_widgets.append((multiple_cb, None))
       +
       +        def fmt_docs(key, klass):
       +            lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
       +            return '\n'.join([key, "", " ".join(lines)])
       +
       +        choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
       +        if len(choosers) > 1:
       +            chooser_name = coinchooser.get_name(self.config)
       +            msg = _('Choose coin (UTXO) selection method.  The following are available:\n\n')
       +            msg += '\n\n'.join(fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items())
       +            chooser_label = HelpLabel(_('Coin selection') + ':', msg)
       +            chooser_combo = QComboBox()
       +            chooser_combo.addItems(choosers)
       +            i = choosers.index(chooser_name) if chooser_name in choosers else 0
       +            chooser_combo.setCurrentIndex(i)
       +            def on_chooser(x):
       +                chooser_name = choosers[chooser_combo.currentIndex()]
       +                self.config.set_key('coin_chooser', chooser_name)
       +            chooser_combo.currentIndexChanged.connect(on_chooser)
       +            tx_widgets.append((chooser_label, chooser_combo))
       +
       +        def on_unconf(x):
       +            self.config.set_key('confirmed_only', bool(x))
       +        conf_only = self.config.get('confirmed_only', False)
       +        unconf_cb = QCheckBox(_('Spend only confirmed coins'))
       +        unconf_cb.setToolTip(_('Spend only confirmed inputs.'))
       +        unconf_cb.setChecked(conf_only)
       +        unconf_cb.stateChanged.connect(on_unconf)
       +        tx_widgets.append((unconf_cb, None))
       +
       +        def on_outrounding(x):
       +            self.config.set_key('coin_chooser_output_rounding', bool(x))
       +        enable_outrounding = self.config.get('coin_chooser_output_rounding', False)
       +        outrounding_cb = QCheckBox(_('Enable output value rounding'))
       +        outrounding_cb.setToolTip(
       +            _('Set the value of the change output so that it has similar precision to the other outputs.') + '\n' +
       +            _('This might improve your privacy somewhat.') + '\n' +
       +            _('If enabled, at most 100 satoshis might be lost due to this, per transaction.'))
       +        outrounding_cb.setChecked(enable_outrounding)
       +        outrounding_cb.stateChanged.connect(on_outrounding)
       +        tx_widgets.append((outrounding_cb, None))
       +
       +        # Fiat Currency
       +        hist_checkbox = QCheckBox()
       +        hist_capgains_checkbox = QCheckBox()
       +        fiat_address_checkbox = QCheckBox()
       +        ccy_combo = QComboBox()
       +        ex_combo = QComboBox()
       +
       +        def update_currencies():
       +            if not self.fx: return
       +            currencies = sorted(self.fx.get_currencies(self.fx.get_history_config()))
       +            ccy_combo.clear()
       +            ccy_combo.addItems([_('None')] + currencies)
       +            if self.fx.is_enabled():
       +                ccy_combo.setCurrentIndex(ccy_combo.findText(self.fx.get_currency()))
       +
       +        def update_history_cb():
       +            if not self.fx: return
       +            hist_checkbox.setChecked(self.fx.get_history_config())
       +            hist_checkbox.setEnabled(self.fx.is_enabled())
       +
       +        def update_fiat_address_cb():
       +            if not self.fx: return
       +            fiat_address_checkbox.setChecked(self.fx.get_fiat_address_config())
       +
       +        def update_history_capgains_cb():
       +            if not self.fx: return
       +            hist_capgains_checkbox.setChecked(self.fx.get_history_capital_gains_config())
       +            hist_capgains_checkbox.setEnabled(hist_checkbox.isChecked())
       +
       +        def update_exchanges():
       +            if not self.fx: return
       +            b = self.fx.is_enabled()
       +            ex_combo.setEnabled(b)
       +            if b:
       +                h = self.fx.get_history_config()
       +                c = self.fx.get_currency()
       +                exchanges = self.fx.get_exchanges_by_ccy(c, h)
       +            else:
       +                exchanges = self.fx.get_exchanges_by_ccy('USD', False)
       +            ex_combo.clear()
       +            ex_combo.addItems(sorted(exchanges))
       +            ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange()))
       +
       +        def on_currency(hh):
       +            if not self.fx: return
       +            b = bool(ccy_combo.currentIndex())
       +            ccy = str(ccy_combo.currentText()) if b else None
       +            self.fx.set_enabled(b)
       +            if b and ccy != self.fx.ccy:
       +                self.fx.set_currency(ccy)
       +            update_history_cb()
       +            update_exchanges()
       +            self.update_fiat()
       +
       +        def on_exchange(idx):
       +            exchange = str(ex_combo.currentText())
       +            if self.fx and self.fx.is_enabled() and exchange and exchange != self.fx.exchange.name():
       +                self.fx.set_exchange(exchange)
       +
       +        def on_history(checked):
       +            if not self.fx: return
       +            self.fx.set_history_config(checked)
       +            update_exchanges()
       +            self.history_list.refresh_headers()
       +            if self.fx.is_enabled() and checked:
       +                # reset timeout to get historical rates
       +                self.fx.timeout = 0
       +            update_history_capgains_cb()
       +
       +        def on_history_capgains(checked):
       +            if not self.fx: return
       +            self.fx.set_history_capital_gains_config(checked)
       +            self.history_list.refresh_headers()
       +
       +        def on_fiat_address(checked):
       +            if not self.fx: return
       +            self.fx.set_fiat_address_config(checked)
       +            self.address_list.refresh_headers()
       +            self.address_list.update()
       +
       +        update_currencies()
       +        update_history_cb()
       +        update_history_capgains_cb()
       +        update_fiat_address_cb()
       +        update_exchanges()
       +        ccy_combo.currentIndexChanged.connect(on_currency)
       +        hist_checkbox.stateChanged.connect(on_history)
       +        hist_capgains_checkbox.stateChanged.connect(on_history_capgains)
       +        fiat_address_checkbox.stateChanged.connect(on_fiat_address)
       +        ex_combo.currentIndexChanged.connect(on_exchange)
       +
       +        fiat_widgets = []
       +        fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
       +        fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
       +        fiat_widgets.append((QLabel(_('Show capital gains in history')), hist_capgains_checkbox))
       +        fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')), fiat_address_checkbox))
       +        fiat_widgets.append((QLabel(_('Source')), ex_combo))
       +
       +        tabs_info = [
       +            (fee_widgets, _('Fees')),
       +            (tx_widgets, _('Transactions')),
       +            (gui_widgets, _('Appearance')),
       +            (fiat_widgets, _('Fiat')),
       +            (id_widgets, _('Identity')),
       +        ]
       +        for widgets, name in tabs_info:
       +            tab = QWidget()
       +            grid = QGridLayout(tab)
       +            grid.setColumnStretch(0,1)
       +            for a,b in widgets:
       +                i = grid.rowCount()
       +                if b:
       +                    if a:
       +                        grid.addWidget(a, i, 0)
       +                    grid.addWidget(b, i, 1)
       +                else:
       +                    grid.addWidget(a, i, 0, 1, 2)
       +            tabs.addTab(tab, name)
       +
       +        vbox.addWidget(tabs)
       +        vbox.addStretch(1)
       +        vbox.addLayout(Buttons(CloseButton(d)))
       +        d.setLayout(vbox)
       +
       +        # run the dialog
       +        d.exec_()
       +
       +        if self.fx:
       +            self.fx.timeout = 0
       +
       +        self.alias_received_signal.disconnect(set_alias_color)
       +
       +        run_hook('close_settings_dialog')
       +        if self.need_restart:
       +            self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))
       +
       +
       +    def closeEvent(self, event):
       +        # It seems in some rare cases this closeEvent() is called twice
       +        if not self.cleaned_up:
       +            self.cleaned_up = True
       +            self.clean_up()
       +        event.accept()
       +
       +    def clean_up(self):
       +        self.wallet.thread.stop()
       +        if self.network:
       +            self.network.unregister_callback(self.on_network)
       +        self.config.set_key("is_maximized", self.isMaximized())
       +        if not self.isMaximized():
       +            g = self.geometry()
       +            self.wallet.storage.put("winpos-qt", [g.left(),g.top(),
       +                                                  g.width(),g.height()])
       +        self.config.set_key("console-history", self.console.history[-50:],
       +                            True)
       +        if self.qr_window:
       +            self.qr_window.close()
       +        self.close_wallet()
       +        self.gui_object.close_window(self)
       +
       +    def plugins_dialog(self):
       +        self.pluginsdialog = d = WindowModalDialog(self, _('Electrum Plugins'))
       +
       +        plugins = self.gui_object.plugins
       +
       +        vbox = QVBoxLayout(d)
       +
       +        # plugins
       +        scroll = QScrollArea()
       +        scroll.setEnabled(True)
       +        scroll.setWidgetResizable(True)
       +        scroll.setMinimumSize(400,250)
       +        vbox.addWidget(scroll)
       +
       +        w = QWidget()
       +        scroll.setWidget(w)
       +        w.setMinimumHeight(plugins.count() * 35)
       +
       +        grid = QGridLayout()
       +        grid.setColumnStretch(0,1)
       +        w.setLayout(grid)
       +
       +        settings_widgets = {}
       +
       +        def enable_settings_widget(p, name, i):
       +            widget = settings_widgets.get(name)
       +            if not widget and p and p.requires_settings():
       +                widget = settings_widgets[name] = p.settings_widget(d)
       +                grid.addWidget(widget, i, 1)
       +            if widget:
       +                widget.setEnabled(bool(p and p.is_enabled()))
       +
       +        def do_toggle(cb, name, i):
       +            p = plugins.toggle(name)
       +            cb.setChecked(bool(p))
       +            enable_settings_widget(p, name, i)
       +            run_hook('init_qt', self.gui_object)
       +
       +        for i, descr in enumerate(plugins.descriptions.values()):
       +            full_name = descr['__name__']
       +            prefix, _separator, name = full_name.rpartition('.')
       +            p = plugins.get(name)
       +            if descr.get('registers_keystore'):
       +                continue
       +            try:
       +                cb = QCheckBox(descr['fullname'])
       +                plugin_is_loaded = p is not None
       +                cb_enabled = (not plugin_is_loaded and plugins.is_available(name, self.wallet)
       +                              or plugin_is_loaded and p.can_user_disable())
       +                cb.setEnabled(cb_enabled)
       +                cb.setChecked(plugin_is_loaded and p.is_enabled())
       +                grid.addWidget(cb, i, 0)
       +                enable_settings_widget(p, name, i)
       +                cb.clicked.connect(partial(do_toggle, cb, name, i))
       +                msg = descr['description']
       +                if descr.get('requires'):
       +                    msg += '\n\n' + _('Requires') + ':\n' + '\n'.join(map(lambda x: x[1], descr.get('requires')))
       +                grid.addWidget(HelpButton(msg), i, 2)
       +            except Exception:
       +                self.print_msg("error: cannot display plugin", name)
       +                traceback.print_exc(file=sys.stdout)
       +        grid.setRowStretch(len(plugins.descriptions.values()), 1)
       +        vbox.addLayout(Buttons(CloseButton(d)))
       +        d.exec_()
       +
       +    def cpfp(self, parent_tx, new_tx):
       +        total_size = parent_tx.estimated_size() + new_tx.estimated_size()
       +        d = WindowModalDialog(self, _('Child Pays for Parent'))
       +        vbox = QVBoxLayout(d)
       +        msg = (
       +            "A CPFP is a transaction that sends an unconfirmed output back to "
       +            "yourself, with a high fee. The goal is to have miners confirm "
       +            "the parent transaction in order to get the fee attached to the "
       +            "child transaction.")
       +        vbox.addWidget(WWLabel(_(msg)))
       +        msg2 = ("The proposed fee is computed using your "
       +            "fee/kB settings, applied to the total size of both child and "
       +            "parent transactions. After you broadcast a CPFP transaction, "
       +            "it is normal to see a new unconfirmed transaction in your history.")
       +        vbox.addWidget(WWLabel(_(msg2)))
       +        grid = QGridLayout()
       +        grid.addWidget(QLabel(_('Total size') + ':'), 0, 0)
       +        grid.addWidget(QLabel('%d bytes'% total_size), 0, 1)
       +        max_fee = new_tx.output_value()
       +        grid.addWidget(QLabel(_('Input amount') + ':'), 1, 0)
       +        grid.addWidget(QLabel(self.format_amount(max_fee) + ' ' + self.base_unit()), 1, 1)
       +        output_amount = QLabel('')
       +        grid.addWidget(QLabel(_('Output amount') + ':'), 2, 0)
       +        grid.addWidget(output_amount, 2, 1)
       +        fee_e = BTCAmountEdit(self.get_decimal_point)
       +        # FIXME with dyn fees, without estimates, there are all kinds of crashes here
       +        def f(x):
       +            a = max_fee - fee_e.get_amount()
       +            output_amount.setText((self.format_amount(a) + ' ' + self.base_unit()) if a else '')
       +        fee_e.textChanged.connect(f)
       +        fee = self.config.fee_per_kb() * total_size / 1000
       +        fee_e.setAmount(fee)
       +        grid.addWidget(QLabel(_('Fee' + ':')), 3, 0)
       +        grid.addWidget(fee_e, 3, 1)
       +        def on_rate(dyn, pos, fee_rate):
       +            fee = fee_rate * total_size / 1000
       +            fee = min(max_fee, fee)
       +            fee_e.setAmount(fee)
       +        fee_slider = FeeSlider(self, self.config, on_rate)
       +        fee_slider.update()
       +        grid.addWidget(fee_slider, 4, 1)
       +        vbox.addLayout(grid)
       +        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
       +        if not d.exec_():
       +            return
       +        fee = fee_e.get_amount()
       +        if fee > max_fee:
       +            self.show_error(_('Max fee exceeded'))
       +            return
       +        new_tx = self.wallet.cpfp(parent_tx, fee)
       +        new_tx.set_rbf(True)
       +        self.show_transaction(new_tx)
       +
       +    def bump_fee_dialog(self, tx):
       +        is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
       +        if fee is None:
       +            self.show_error(_("Can't bump fee: unknown fee for original transaction."))
       +            return
       +        tx_label = self.wallet.get_label(tx.txid())
       +        tx_size = tx.estimated_size()
       +        d = WindowModalDialog(self, _('Bump Fee'))
       +        vbox = QVBoxLayout(d)
       +        vbox.addWidget(QLabel(_('Current fee') + ': %s'% self.format_amount(fee) + ' ' + self.base_unit()))
       +        vbox.addWidget(QLabel(_('New fee' + ':')))
       +
       +        fee_e = BTCAmountEdit(self.get_decimal_point)
       +        fee_e.setAmount(fee * 1.5)
       +        vbox.addWidget(fee_e)
       +
       +        def on_rate(dyn, pos, fee_rate):
       +            fee = fee_rate * tx_size / 1000
       +            fee_e.setAmount(fee)
       +        fee_slider = FeeSlider(self, self.config, on_rate)
       +        vbox.addWidget(fee_slider)
       +        cb = QCheckBox(_('Final'))
       +        vbox.addWidget(cb)
       +        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
       +        if not d.exec_():
       +            return
       +        is_final = cb.isChecked()
       +        new_fee = fee_e.get_amount()
       +        delta = new_fee - fee
       +        if delta < 0:
       +            self.show_error("fee too low")
       +            return
       +        try:
       +            new_tx = self.wallet.bump_fee(tx, delta)
       +        except CannotBumpFee as e:
       +            self.show_error(str(e))
       +            return
       +        if is_final:
       +            new_tx.set_rbf(False)
       +        self.show_transaction(new_tx, tx_label)
       +
       +    def save_transaction_into_wallet(self, tx):
       +        win = self.top_level_window()
       +        try:
       +            if not self.wallet.add_transaction(tx.txid(), tx):
       +                win.show_error(_("Transaction could not be saved.") + "\n" +
       +                               _("It conflicts with current history."))
       +                return False
       +        except AddTransactionException as e:
       +            win.show_error(e)
       +            return False
       +        else:
       +            self.wallet.save_transactions(write=True)
       +            # need to update at least: history_list, utxo_list, address_list
       +            self.need_update.set()
       +            msg = (_("Transaction added to wallet history.") + '\n\n' +
       +                   _("Note: this is an offline transaction, if you want the network "
       +                     "to see it, you need to broadcast it."))
       +            win.msg_box(QPixmap(":icons/offline_tx.png"), None, _('Success'), msg)
       +            return True
   DIR diff --git a/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py
   DIR diff --git a/electrum/gui/qt/password_dialog.py b/electrum/gui/qt/password_dialog.py
       t@@ -0,0 +1,305 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2013 ecdsa@github
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from PyQt5.QtCore import Qt
       +from PyQt5.QtGui import *
       +from PyQt5.QtWidgets import *
       +from electrum.i18n import _
       +from .util import *
       +import re
       +import math
       +
       +from electrum.plugin import run_hook
       +
       +def check_password_strength(password):
       +
       +    '''
       +    Check the strength of the password entered by the user and return back the same
       +    :param password: password entered by user in New Password
       +    :return: password strength Weak or Medium or Strong
       +    '''
       +    password = password
       +    n = math.log(len(set(password)))
       +    num = re.search("[0-9]", password) is not None and re.match("^[0-9]*$", password) is None
       +    caps = password != password.upper() and password != password.lower()
       +    extra = re.match("^[a-zA-Z0-9]*$", password) is None
       +    score = len(password)*( n + caps + num + extra)/20
       +    password_strength = {0:"Weak",1:"Medium",2:"Strong",3:"Very Strong"}
       +    return password_strength[min(3, int(score))]
       +
       +
       +PW_NEW, PW_CHANGE, PW_PASSPHRASE = range(0, 3)
       +
       +
       +class PasswordLayout(object):
       +
       +    titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")]
       +
       +    def __init__(self, wallet, msg, kind, OK_button, force_disable_encrypt_cb=False):
       +        self.wallet = wallet
       +
       +        self.pw = QLineEdit()
       +        self.pw.setEchoMode(2)
       +        self.new_pw = QLineEdit()
       +        self.new_pw.setEchoMode(2)
       +        self.conf_pw = QLineEdit()
       +        self.conf_pw.setEchoMode(2)
       +        self.kind = kind
       +        self.OK_button = OK_button
       +
       +        vbox = QVBoxLayout()
       +        label = QLabel(msg + "\n")
       +        label.setWordWrap(True)
       +
       +        grid = QGridLayout()
       +        grid.setSpacing(8)
       +        grid.setColumnMinimumWidth(0, 150)
       +        grid.setColumnMinimumWidth(1, 100)
       +        grid.setColumnStretch(1,1)
       +
       +        if kind == PW_PASSPHRASE:
       +            vbox.addWidget(label)
       +            msgs = [_('Passphrase:'), _('Confirm Passphrase:')]
       +        else:
       +            logo_grid = QGridLayout()
       +            logo_grid.setSpacing(8)
       +            logo_grid.setColumnMinimumWidth(0, 70)
       +            logo_grid.setColumnStretch(1,1)
       +
       +            logo = QLabel()
       +            logo.setAlignment(Qt.AlignCenter)
       +
       +            logo_grid.addWidget(logo,  0, 0)
       +            logo_grid.addWidget(label, 0, 1, 1, 2)
       +            vbox.addLayout(logo_grid)
       +
       +            m1 = _('New Password:') if kind == PW_CHANGE else _('Password:')
       +            msgs = [m1, _('Confirm Password:')]
       +            if wallet and wallet.has_password():
       +                grid.addWidget(QLabel(_('Current Password:')), 0, 0)
       +                grid.addWidget(self.pw, 0, 1)
       +                lockfile = ":icons/lock.png"
       +            else:
       +                lockfile = ":icons/unlock.png"
       +            logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation))
       +
       +        grid.addWidget(QLabel(msgs[0]), 1, 0)
       +        grid.addWidget(self.new_pw, 1, 1)
       +
       +        grid.addWidget(QLabel(msgs[1]), 2, 0)
       +        grid.addWidget(self.conf_pw, 2, 1)
       +        vbox.addLayout(grid)
       +
       +        # Password Strength Label
       +        if kind != PW_PASSPHRASE:
       +            self.pw_strength = QLabel()
       +            grid.addWidget(self.pw_strength, 3, 0, 1, 2)
       +            self.new_pw.textChanged.connect(self.pw_changed)
       +
       +        self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
       +        self.encrypt_cb.setEnabled(False)
       +        grid.addWidget(self.encrypt_cb, 4, 0, 1, 2)
       +        self.encrypt_cb.setVisible(kind != PW_PASSPHRASE)
       +
       +        def enable_OK():
       +            ok = self.new_pw.text() == self.conf_pw.text()
       +            OK_button.setEnabled(ok)
       +            self.encrypt_cb.setEnabled(ok and bool(self.new_pw.text())
       +                                       and not force_disable_encrypt_cb)
       +        self.new_pw.textChanged.connect(enable_OK)
       +        self.conf_pw.textChanged.connect(enable_OK)
       +
       +        self.vbox = vbox
       +
       +    def title(self):
       +        return self.titles[self.kind]
       +
       +    def layout(self):
       +        return self.vbox
       +
       +    def pw_changed(self):
       +        password = self.new_pw.text()
       +        if password:
       +            colors = {"Weak":"Red", "Medium":"Blue", "Strong":"Green",
       +                      "Very Strong":"Green"}
       +            strength = check_password_strength(password)
       +            label = (_("Password Strength") + ": " + "<font color="
       +                     + colors[strength] + ">" + strength + "</font>")
       +        else:
       +            label = ""
       +        self.pw_strength.setText(label)
       +
       +    def old_password(self):
       +        if self.kind == PW_CHANGE:
       +            return self.pw.text() or None
       +        return None
       +
       +    def new_password(self):
       +        pw = self.new_pw.text()
       +        # Empty passphrases are fine and returned empty.
       +        if pw == "" and self.kind != PW_PASSPHRASE:
       +            pw = None
       +        return pw
       +
       +
       +class PasswordLayoutForHW(object):
       +
       +    def __init__(self, wallet, msg, kind, OK_button):
       +        self.wallet = wallet
       +
       +        self.kind = kind
       +        self.OK_button = OK_button
       +
       +        vbox = QVBoxLayout()
       +        label = QLabel(msg + "\n")
       +        label.setWordWrap(True)
       +
       +        grid = QGridLayout()
       +        grid.setSpacing(8)
       +        grid.setColumnMinimumWidth(0, 150)
       +        grid.setColumnMinimumWidth(1, 100)
       +        grid.setColumnStretch(1,1)
       +
       +        logo_grid = QGridLayout()
       +        logo_grid.setSpacing(8)
       +        logo_grid.setColumnMinimumWidth(0, 70)
       +        logo_grid.setColumnStretch(1,1)
       +
       +        logo = QLabel()
       +        logo.setAlignment(Qt.AlignCenter)
       +
       +        logo_grid.addWidget(logo,  0, 0)
       +        logo_grid.addWidget(label, 0, 1, 1, 2)
       +        vbox.addLayout(logo_grid)
       +
       +        if wallet and wallet.has_storage_encryption():
       +            lockfile = ":icons/lock.png"
       +        else:
       +            lockfile = ":icons/unlock.png"
       +        logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation))
       +
       +        vbox.addLayout(grid)
       +
       +        self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
       +        grid.addWidget(self.encrypt_cb, 1, 0, 1, 2)
       +
       +        self.vbox = vbox
       +
       +    def title(self):
       +        return _("Toggle Encryption")
       +
       +    def layout(self):
       +        return self.vbox
       +
       +
       +class ChangePasswordDialogBase(WindowModalDialog):
       +
       +    def __init__(self, parent, wallet):
       +        WindowModalDialog.__init__(self, parent)
       +        is_encrypted = wallet.has_storage_encryption()
       +        OK_button = OkButton(self)
       +
       +        self.create_password_layout(wallet, is_encrypted, OK_button)
       +
       +        self.setWindowTitle(self.playout.title())
       +        vbox = QVBoxLayout(self)
       +        vbox.addLayout(self.playout.layout())
       +        vbox.addStretch(1)
       +        vbox.addLayout(Buttons(CancelButton(self), OK_button))
       +        self.playout.encrypt_cb.setChecked(is_encrypted)
       +
       +    def create_password_layout(self, wallet, is_encrypted, OK_button):
       +        raise NotImplementedError()
       +
       +
       +class ChangePasswordDialogForSW(ChangePasswordDialogBase):
       +
       +    def __init__(self, parent, wallet):
       +        ChangePasswordDialogBase.__init__(self, parent, wallet)
       +        if not wallet.has_password():
       +            self.playout.encrypt_cb.setChecked(True)
       +
       +    def create_password_layout(self, wallet, is_encrypted, OK_button):
       +        if not wallet.has_password():
       +            msg = _('Your wallet is not protected.')
       +            msg += ' ' + _('Use this dialog to add a password to your wallet.')
       +        else:
       +            if not is_encrypted:
       +                msg = _('Your bitcoins are password protected. However, your wallet file is not encrypted.')
       +            else:
       +                msg = _('Your wallet is password protected and encrypted.')
       +            msg += ' ' + _('Use this dialog to change your password.')
       +        self.playout = PasswordLayout(
       +            wallet, msg, PW_CHANGE, OK_button,
       +            force_disable_encrypt_cb=not wallet.can_have_keystore_encryption())
       +
       +    def run(self):
       +        if not self.exec_():
       +            return False, None, None, None
       +        return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
       +
       +
       +class ChangePasswordDialogForHW(ChangePasswordDialogBase):
       +
       +    def __init__(self, parent, wallet):
       +        ChangePasswordDialogBase.__init__(self, parent, wallet)
       +
       +    def create_password_layout(self, wallet, is_encrypted, OK_button):
       +        if not is_encrypted:
       +            msg = _('Your wallet file is NOT encrypted.')
       +        else:
       +            msg = _('Your wallet file is encrypted.')
       +        msg += '\n' + _('Note: If you enable this setting, you will need your hardware device to open your wallet.')
       +        msg += '\n' + _('Use this dialog to toggle encryption.')
       +        self.playout = PasswordLayoutForHW(wallet, msg, PW_CHANGE, OK_button)
       +
       +    def run(self):
       +        if not self.exec_():
       +            return False, None
       +        return True, self.playout.encrypt_cb.isChecked()
       +
       +
       +class PasswordDialog(WindowModalDialog):
       +
       +    def __init__(self, parent=None, msg=None):
       +        msg = msg or _('Please enter your password')
       +        WindowModalDialog.__init__(self, parent, _("Enter Password"))
       +        self.pw = pw = QLineEdit()
       +        pw.setEchoMode(2)
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(QLabel(msg))
       +        grid = QGridLayout()
       +        grid.setSpacing(8)
       +        grid.addWidget(QLabel(_('Password')), 1, 0)
       +        grid.addWidget(pw, 1, 1)
       +        vbox.addLayout(grid)
       +        vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))
       +        self.setLayout(vbox)
       +        run_hook('password_dialog', pw, grid, 1)
       +
       +    def run(self):
       +        if not self.exec_():
       +            return
       +        return self.pw.text()
   DIR diff --git a/gui/qt/paytoedit.py b/electrum/gui/qt/paytoedit.py
   DIR diff --git a/gui/qt/qrcodewidget.py b/electrum/gui/qt/qrcodewidget.py
   DIR diff --git a/electrum/gui/qt/qrtextedit.py b/electrum/gui/qt/qrtextedit.py
       t@@ -0,0 +1,76 @@
       +
       +from electrum.i18n import _
       +from electrum.plugin import run_hook
       +from PyQt5.QtGui import *
       +from PyQt5.QtCore import *
       +from PyQt5.QtWidgets import QFileDialog
       +
       +from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme
       +
       +
       +class ShowQRTextEdit(ButtonsTextEdit):
       +
       +    def __init__(self, text=None):
       +        ButtonsTextEdit.__init__(self, text)
       +        self.setReadOnly(1)
       +        self.addButton(":icons/qrcode.png", self.qr_show, _("Show as QR code"))
       +
       +        run_hook('show_text_edit', self)
       +
       +    def qr_show(self):
       +        from .qrcodewidget import QRDialog
       +        try:
       +            s = str(self.toPlainText())
       +        except:
       +            s = self.toPlainText()
       +        QRDialog(s).exec_()
       +
       +    def contextMenuEvent(self, e):
       +        m = self.createStandardContextMenu()
       +        m.addAction(_("Show as QR code"), self.qr_show)
       +        m.exec_(e.globalPos())
       +
       +
       +class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
       +
       +    def __init__(self, text="", allow_multi=False):
       +        ButtonsTextEdit.__init__(self, text)
       +        self.allow_multi = allow_multi
       +        self.setReadOnly(0)
       +        self.addButton(":icons/file.png", self.file_input, _("Read file"))
       +        icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png"
       +        self.addButton(icon, self.qr_input, _("Read QR code"))
       +        run_hook('scan_text_edit', self)
       +
       +    def file_input(self):
       +        fileName, __ = QFileDialog.getOpenFileName(self, 'select file')
       +        if not fileName:
       +            return
       +        try:
       +            with open(fileName, "r") as f:
       +                data = f.read()
       +        except BaseException as e:
       +            self.show_error(_('Error opening file') + ':\n' + str(e))
       +        else:
       +            self.setText(data)
       +
       +    def qr_input(self):
       +        from electrum import qrscanner, get_config
       +        try:
       +            data = qrscanner.scan_barcode(get_config().get_video_device())
       +        except BaseException as e:
       +            self.show_error(str(e))
       +            data = ''
       +        if not data:
       +            data = ''
       +        if self.allow_multi:
       +            new_text = self.text() + data + '\n'
       +        else:
       +            new_text = data
       +        self.setText(new_text)
       +        return data
       +
       +    def contextMenuEvent(self, e):
       +        m = self.createStandardContextMenu()
       +        m.addAction(_("Read QR code"), self.qr_input)
       +        m.exec_(e.globalPos())
   DIR diff --git a/electrum/gui/qt/qrwindow.py b/electrum/gui/qt/qrwindow.py
       t@@ -0,0 +1,89 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2014 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +import platform
       +
       +from PyQt5.QtCore import Qt
       +from PyQt5.QtGui import *
       +from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget
       +
       +from .qrcodewidget import QRCodeWidget
       +from electrum.i18n import _
       +
       +if platform.system() == 'Windows':
       +    MONOSPACE_FONT = 'Lucida Console'
       +elif platform.system() == 'Darwin':
       +    MONOSPACE_FONT = 'Monaco'
       +else:
       +    MONOSPACE_FONT = 'monospace'
       +
       +column_index = 4
       +
       +class QR_Window(QWidget):
       +
       +    def __init__(self, win):
       +        QWidget.__init__(self)
       +        self.win = win
       +        self.setWindowTitle('Electrum - '+_('Payment Request'))
       +        self.setMinimumSize(800, 250)
       +        self.address = ''
       +        self.label = ''
       +        self.amount = 0
       +        self.setFocusPolicy(Qt.NoFocus)
       +
       +        main_box = QHBoxLayout()
       +
       +        self.qrw = QRCodeWidget()
       +        main_box.addWidget(self.qrw, 1)
       +
       +        vbox = QVBoxLayout()
       +        main_box.addLayout(vbox)
       +
       +        self.address_label = QLabel("")
       +        #self.address_label.setFont(QFont(MONOSPACE_FONT))
       +        vbox.addWidget(self.address_label)
       +
       +        self.label_label = QLabel("")
       +        vbox.addWidget(self.label_label)
       +
       +        self.amount_label = QLabel("")
       +        vbox.addWidget(self.amount_label)
       +
       +        vbox.addStretch(1)
       +        self.setLayout(main_box)
       +
       +
       +    def set_content(self, address, amount, message, url):
       +        address_text = "<span style='font-size: 18pt'>%s</span>" % address if address else ""
       +        self.address_label.setText(address_text)
       +        if amount:
       +            amount = self.win.format_amount(amount)
       +            amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>%s</span> " % (amount, self.win.base_unit())
       +        else:
       +            amount_text = ''
       +        self.amount_label.setText(amount_text)
       +        label_text = "<span style='font-size: 21pt'>%s</span>" % message if message else ""
       +        self.label_label.setText(label_text)
       +        self.qrw.setData(url)
   DIR diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
       t@@ -0,0 +1,129 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2015 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from electrum.i18n import _
       +from electrum.util import format_time, age
       +from electrum.plugin import run_hook
       +from electrum.paymentrequest import PR_UNKNOWN
       +from PyQt5.QtGui import *
       +from PyQt5.QtCore import *
       +from PyQt5.QtWidgets import QTreeWidgetItem, QMenu
       +from .util import MyTreeWidget, pr_tooltips, pr_icons
       +
       +
       +class RequestList(MyTreeWidget):
       +    filter_columns = [0, 1, 2, 3, 4]  # Date, Account, Address, Description, Amount
       +
       +
       +    def __init__(self, parent):
       +        MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 3)
       +        self.currentItemChanged.connect(self.item_changed)
       +        self.itemClicked.connect(self.item_changed)
       +        self.setSortingEnabled(True)
       +        self.setColumnWidth(0, 180)
       +        self.hideColumn(1)
       +
       +    def item_changed(self, item):
       +        if item is None:
       +            return
       +        if not item.isSelected():
       +            return
       +        addr = str(item.text(1))
       +        req = self.wallet.receive_requests.get(addr)
       +        if req is None:
       +            self.update()
       +            return
       +        expires = age(req['time'] + req['exp']) if req.get('exp') else _('Never')
       +        amount = req['amount']
       +        message = self.wallet.labels.get(addr, '')
       +        self.parent.receive_address_e.setText(addr)
       +        self.parent.receive_message_e.setText(message)
       +        self.parent.receive_amount_e.setAmount(amount)
       +        self.parent.expires_combo.hide()
       +        self.parent.expires_label.show()
       +        self.parent.expires_label.setText(expires)
       +        self.parent.new_request_button.setEnabled(True)
       +
       +    def on_update(self):
       +        self.wallet = self.parent.wallet
       +        # hide receive tab if no receive requests available
       +        b = len(self.wallet.receive_requests) > 0
       +        self.setVisible(b)
       +        self.parent.receive_requests_label.setVisible(b)
       +        if not b:
       +            self.parent.expires_label.hide()
       +            self.parent.expires_combo.show()
       +
       +        # update the receive address if necessary
       +        current_address = self.parent.receive_address_e.text()
       +        domain = self.wallet.get_receiving_addresses()
       +        addr = self.wallet.get_unused_address()
       +        if not current_address in domain and addr:
       +            self.parent.set_receive_address(addr)
       +        self.parent.new_request_button.setEnabled(addr != current_address)
       +
       +        # clear the list and fill it again
       +        self.clear()
       +        for req in self.wallet.get_sorted_requests(self.config):
       +            address = req['address']
       +            if address not in domain:
       +                continue
       +            timestamp = req.get('time', 0)
       +            amount = req.get('amount')
       +            expiration = req.get('exp', None)
       +            message = req.get('memo', '')
       +            date = format_time(timestamp)
       +            status = req.get('status')
       +            signature = req.get('sig')
       +            requestor = req.get('name', '')
       +            amount_str = self.parent.format_amount(amount) if amount else ""
       +            item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')])
       +            if signature is not None:
       +                item.setIcon(2, self.icon_cache.get(":icons/seal.png"))
       +                item.setToolTip(2, 'signed by '+ requestor)
       +            if status is not PR_UNKNOWN:
       +                item.setIcon(6, self.icon_cache.get(pr_icons.get(status)))
       +            self.addTopLevelItem(item)
       +
       +
       +    def create_menu(self, position):
       +        item = self.itemAt(position)
       +        if not item:
       +            return
       +        addr = str(item.text(1))
       +        req = self.wallet.receive_requests.get(addr)
       +        if req is None:
       +            self.update()
       +            return
       +        column = self.currentColumn()
       +        column_title = self.headerItem().text(column)
       +        column_data = item.text(column)
       +        menu = QMenu(self)
       +        menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
       +        menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))
       +        menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
       +        menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
       +        run_hook('receive_list_menu', menu, addr)
       +        menu.exec_(self.viewport().mapToGlobal(position))
   DIR diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py
       t@@ -0,0 +1,211 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2013 ecdsa@github
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from electrum.i18n import _
       +from electrum.mnemonic import Mnemonic
       +import electrum.old_mnemonic
       +from electrum.plugin import run_hook
       +
       +
       +from .util import *
       +from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
       +from .completion_text_edit import CompletionTextEdit
       +
       +
       +def seed_warning_msg(seed):
       +    return ''.join([
       +        "<p>",
       +        _("Please save these {0} words on paper (order is important). "),
       +        _("This seed will allow you to recover your wallet in case "
       +          "of computer failure."),
       +        "</p>",
       +        "<b>" + _("WARNING") + ":</b>",
       +        "<ul>",
       +        "<li>" + _("Never disclose your seed.") + "</li>",
       +        "<li>" + _("Never type it on a website.") + "</li>",
       +        "<li>" + _("Do not store it electronically.") + "</li>",
       +        "</ul>"
       +    ]).format(len(seed.split()))
       +
       +
       +class SeedLayout(QVBoxLayout):
       +
       +    def seed_options(self):
       +        dialog = QDialog()
       +        vbox = QVBoxLayout(dialog)
       +        if 'ext' in self.options:
       +            cb_ext = QCheckBox(_('Extend this seed with custom words'))
       +            cb_ext.setChecked(self.is_ext)
       +            vbox.addWidget(cb_ext)
       +        if 'bip39' in self.options:
       +            def f(b):
       +                self.is_seed = (lambda x: bool(x)) if b else self.saved_is_seed
       +                self.is_bip39 = b
       +                self.on_edit()
       +                if b:
       +                    msg = ' '.join([
       +                        '<b>' + _('Warning') + ':</b>  ',
       +                        _('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
       +                        _('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),
       +                        _('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),
       +                        _('We do not guarantee that BIP39 imports will always be supported in Electrum.'),
       +                    ])
       +                else:
       +                    msg = ''
       +                self.seed_warning.setText(msg)
       +            cb_bip39 = QCheckBox(_('BIP39 seed'))
       +            cb_bip39.toggled.connect(f)
       +            cb_bip39.setChecked(self.is_bip39)
       +            vbox.addWidget(cb_bip39)
       +        vbox.addLayout(Buttons(OkButton(dialog)))
       +        if not dialog.exec_():
       +            return None
       +        self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False
       +        self.is_bip39 = cb_bip39.isChecked() if 'bip39' in self.options else False
       +
       +    def __init__(self, seed=None, title=None, icon=True, msg=None, options=None,
       +                 is_seed=None, passphrase=None, parent=None, for_seed_words=True):
       +        QVBoxLayout.__init__(self)
       +        self.parent = parent
       +        self.options = options
       +        if title:
       +            self.addWidget(WWLabel(title))
       +        if seed:  # "read only", we already have the text
       +            if for_seed_words:
       +                self.seed_e = ButtonsTextEdit()
       +            else:  # e.g. xpub
       +                self.seed_e = ShowQRTextEdit()
       +            self.seed_e.setReadOnly(True)
       +            self.seed_e.setText(seed)
       +        else:  # we expect user to enter text
       +            assert for_seed_words
       +            self.seed_e = CompletionTextEdit()
       +            self.seed_e.setTabChangesFocus(False)  # so that tab auto-completes
       +            self.is_seed = is_seed
       +            self.saved_is_seed = self.is_seed
       +            self.seed_e.textChanged.connect(self.on_edit)
       +            self.initialize_completer()
       +
       +        self.seed_e.setMaximumHeight(75)
       +        hbox = QHBoxLayout()
       +        if icon:
       +            logo = QLabel()
       +            logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(64, mode=Qt.SmoothTransformation))
       +            logo.setMaximumWidth(60)
       +            hbox.addWidget(logo)
       +        hbox.addWidget(self.seed_e)
       +        self.addLayout(hbox)
       +        hbox = QHBoxLayout()
       +        hbox.addStretch(1)
       +        self.seed_type_label = QLabel('')
       +        hbox.addWidget(self.seed_type_label)
       +
       +        # options
       +        self.is_bip39 = False
       +        self.is_ext = False
       +        if options:
       +            opt_button = EnterButton(_('Options'), self.seed_options)
       +            hbox.addWidget(opt_button)
       +            self.addLayout(hbox)
       +        if passphrase:
       +            hbox = QHBoxLayout()
       +            passphrase_e = QLineEdit()
       +            passphrase_e.setText(passphrase)
       +            passphrase_e.setReadOnly(True)
       +            hbox.addWidget(QLabel(_("Your seed extension is") + ':'))
       +            hbox.addWidget(passphrase_e)
       +            self.addLayout(hbox)
       +        self.addStretch(1)
       +        self.seed_warning = WWLabel('')
       +        if msg:
       +            self.seed_warning.setText(seed_warning_msg(seed))
       +        self.addWidget(self.seed_warning)
       +
       +    def initialize_completer(self):
       +        english_list = Mnemonic('en').wordlist
       +        old_list = electrum.old_mnemonic.words
       +        self.wordlist = english_list + list(set(old_list) - set(english_list)) #concat both lists
       +        self.wordlist.sort()
       +        self.completer = QCompleter(self.wordlist)
       +        self.seed_e.set_completer(self.completer)
       +
       +    def get_seed(self):
       +        text = self.seed_e.text()
       +        return ' '.join(text.split())
       +
       +    def on_edit(self):
       +        from electrum.bitcoin import seed_type
       +        s = self.get_seed()
       +        b = self.is_seed(s)
       +        if not self.is_bip39:
       +            t = seed_type(s)
       +            label = _('Seed Type') + ': ' + t if t else ''
       +        else:
       +            from electrum.keystore import bip39_is_checksum_valid
       +            is_checksum, is_wordlist = bip39_is_checksum_valid(s)
       +            status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist'
       +            label = 'BIP39' + ' (%s)'%status
       +        self.seed_type_label.setText(label)
       +        self.parent.next_button.setEnabled(b)
       +
       +        # to account for bip39 seeds
       +        for word in self.get_seed().split(" ")[:-1]:
       +            if word not in self.wordlist:
       +                self.seed_e.disable_suggestions()
       +                return
       +        self.seed_e.enable_suggestions()
       +
       +class KeysLayout(QVBoxLayout):
       +    def __init__(self, parent=None, header_layout=None, is_valid=None, allow_multi=False):
       +        QVBoxLayout.__init__(self)
       +        self.parent = parent
       +        self.is_valid = is_valid
       +        self.text_e = ScanQRTextEdit(allow_multi=allow_multi)
       +        self.text_e.textChanged.connect(self.on_edit)
       +        if isinstance(header_layout, str):
       +            self.addWidget(WWLabel(header_layout))
       +        else:
       +            self.addLayout(header_layout)
       +        self.addWidget(self.text_e)
       +
       +    def get_text(self):
       +        return self.text_e.text()
       +
       +    def on_edit(self):
       +        b = self.is_valid(self.get_text())
       +        self.parent.next_button.setEnabled(b)
       +
       +
       +class SeedDialog(WindowModalDialog):
       +
       +    def __init__(self, parent, seed, passphrase):
       +        WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
       +        self.setMinimumWidth(400)
       +        vbox = QVBoxLayout(self)
       +        title =  _("Your wallet generation seed is:")
       +        slayout = SeedLayout(title=title, seed=seed, msg=True, passphrase=passphrase)
       +        vbox.addLayout(slayout)
       +        run_hook('set_seed', seed, slayout.seed_e)
       +        vbox.addLayout(Buttons(CloseButton(self)))
   DIR diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py
       t@@ -0,0 +1,328 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2012 thomasv@gitorious
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import copy
       +import datetime
       +import json
       +import traceback
       +
       +from PyQt5.QtCore import *
       +from PyQt5.QtGui import *
       +from PyQt5.QtWidgets import *
       +
       +from electrum.bitcoin import base_encode
       +from electrum.i18n import _
       +from electrum.plugin import run_hook
       +from electrum import simple_config
       +
       +from electrum.util import bfh
       +from electrum.wallet import AddTransactionException
       +from electrum.transaction import SerializationError
       +
       +from .util import *
       +
       +
       +SAVE_BUTTON_ENABLED_TOOLTIP = _("Save transaction offline")
       +SAVE_BUTTON_DISABLED_TOOLTIP = _("Please sign this transaction in order to save it")
       +
       +
       +dialogs = []  # Otherwise python randomly garbage collects the dialogs...
       +
       +
       +def show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):
       +    try:
       +        d = TxDialog(tx, parent, desc, prompt_if_unsaved)
       +    except SerializationError as e:
       +        traceback.print_exc(file=sys.stderr)
       +        parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
       +    else:
       +        dialogs.append(d)
       +        d.show()
       +
       +
       +class TxDialog(QDialog, MessageBoxMixin):
       +
       +    def __init__(self, tx, parent, desc, prompt_if_unsaved):
       +        '''Transactions in the wallet will show their description.
       +        Pass desc to give a description for txs not yet in the wallet.
       +        '''
       +        # We want to be a top-level window
       +        QDialog.__init__(self, parent=None)
       +        # Take a copy; it might get updated in the main window by
       +        # e.g. the FX plugin.  If this happens during or after a long
       +        # sign operation the signatures are lost.
       +        self.tx = tx = copy.deepcopy(tx)
       +        try:
       +            self.tx.deserialize()
       +        except BaseException as e:
       +            raise SerializationError(e)
       +        self.main_window = parent
       +        self.wallet = parent.wallet
       +        self.prompt_if_unsaved = prompt_if_unsaved
       +        self.saved = False
       +        self.desc = desc
       +
       +        # if the wallet can populate the inputs with more info, do it now.
       +        # as a result, e.g. we might learn an imported address tx is segwit,
       +        # in which case it's ok to display txid
       +        self.wallet.add_input_info_to_all_inputs(tx)
       +
       +        self.setMinimumWidth(950)
       +        self.setWindowTitle(_("Transaction"))
       +
       +        vbox = QVBoxLayout()
       +        self.setLayout(vbox)
       +
       +        vbox.addWidget(QLabel(_("Transaction ID:")))
       +        self.tx_hash_e  = ButtonsLineEdit()
       +        qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
       +        self.tx_hash_e.addButton(":icons/qrcode.png", qr_show, _("Show as QR code"))
       +        self.tx_hash_e.setReadOnly(True)
       +        vbox.addWidget(self.tx_hash_e)
       +        self.tx_desc = QLabel()
       +        vbox.addWidget(self.tx_desc)
       +        self.status_label = QLabel()
       +        vbox.addWidget(self.status_label)
       +        self.date_label = QLabel()
       +        vbox.addWidget(self.date_label)
       +        self.amount_label = QLabel()
       +        vbox.addWidget(self.amount_label)
       +        self.size_label = QLabel()
       +        vbox.addWidget(self.size_label)
       +        self.fee_label = QLabel()
       +        vbox.addWidget(self.fee_label)
       +
       +        self.add_io(vbox)
       +
       +        vbox.addStretch(1)
       +
       +        self.sign_button = b = QPushButton(_("Sign"))
       +        b.clicked.connect(self.sign)
       +
       +        self.broadcast_button = b = QPushButton(_("Broadcast"))
       +        b.clicked.connect(self.do_broadcast)
       +
       +        self.save_button = b = QPushButton(_("Save"))
       +        save_button_disabled = not tx.is_complete()
       +        b.setDisabled(save_button_disabled)
       +        if save_button_disabled:
       +            b.setToolTip(SAVE_BUTTON_DISABLED_TOOLTIP)
       +        else:
       +            b.setToolTip(SAVE_BUTTON_ENABLED_TOOLTIP)
       +        b.clicked.connect(self.save)
       +
       +        self.export_button = b = QPushButton(_("Export"))
       +        b.clicked.connect(self.export)
       +
       +        self.cancel_button = b = QPushButton(_("Close"))
       +        b.clicked.connect(self.close)
       +        b.setDefault(True)
       +
       +        self.qr_button = b = QPushButton()
       +        b.setIcon(QIcon(":icons/qrcode.png"))
       +        b.clicked.connect(self.show_qr)
       +
       +        self.copy_button = CopyButton(lambda: str(self.tx), parent.app)
       +
       +        # Action buttons
       +        self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button]
       +        # Transaction sharing buttons
       +        self.sharing_buttons = [self.copy_button, self.qr_button, self.export_button, self.save_button]
       +
       +        run_hook('transaction_dialog', self)
       +
       +        hbox = QHBoxLayout()
       +        hbox.addLayout(Buttons(*self.sharing_buttons))
       +        hbox.addStretch(1)
       +        hbox.addLayout(Buttons(*self.buttons))
       +        vbox.addLayout(hbox)
       +        self.update()
       +
       +    def do_broadcast(self):
       +        self.main_window.push_top_level_window(self)
       +        try:
       +            self.main_window.broadcast_transaction(self.tx, self.desc)
       +        finally:
       +            self.main_window.pop_top_level_window(self)
       +        self.saved = True
       +        self.update()
       +
       +    def closeEvent(self, event):
       +        if (self.prompt_if_unsaved and not self.saved
       +                and not self.question(_('This transaction is not saved. Close anyway?'), title=_("Warning"))):
       +            event.ignore()
       +        else:
       +            event.accept()
       +            try:
       +                dialogs.remove(self)
       +            except ValueError:
       +                pass  # was not in list already
       +
       +    def show_qr(self):
       +        text = bfh(str(self.tx))
       +        text = base_encode(text, base=43)
       +        try:
       +            self.main_window.show_qrcode(text, 'Transaction', parent=self)
       +        except Exception as e:
       +            self.show_message(str(e))
       +
       +    def sign(self):
       +        def sign_done(success):
       +            # note: with segwit we could save partially signed tx, because they have a txid
       +            if self.tx.is_complete():
       +                self.prompt_if_unsaved = True
       +                self.saved = False
       +                self.save_button.setDisabled(False)
       +                self.save_button.setToolTip(SAVE_BUTTON_ENABLED_TOOLTIP)
       +            self.update()
       +            self.main_window.pop_top_level_window(self)
       +
       +        self.sign_button.setDisabled(True)
       +        self.main_window.push_top_level_window(self)
       +        self.main_window.sign_tx(self.tx, sign_done)
       +
       +    def save(self):
       +        self.main_window.push_top_level_window(self)
       +        if self.main_window.save_transaction_into_wallet(self.tx):
       +            self.save_button.setDisabled(True)
       +            self.saved = True
       +        self.main_window.pop_top_level_window(self)
       +
       +
       +    def export(self):
       +        name = 'signed_%s.txn' % (self.tx.txid()[0:8]) if self.tx.is_complete() else 'unsigned.txn'
       +        fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn")
       +        if fileName:
       +            with open(fileName, "w+") as f:
       +                f.write(json.dumps(self.tx.as_dict(), indent=4) + '\n')
       +            self.show_message(_("Transaction exported successfully"))
       +            self.saved = True
       +
       +    def update(self):
       +        desc = self.desc
       +        base_unit = self.main_window.base_unit()
       +        format_amount = self.main_window.format_amount
       +        tx_hash, status, label, can_broadcast, can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
       +        size = self.tx.estimated_size()
       +        self.broadcast_button.setEnabled(can_broadcast)
       +        can_sign = not self.tx.is_complete() and \
       +            (self.wallet.can_sign(self.tx) or bool(self.main_window.tx_external_keypairs))
       +        self.sign_button.setEnabled(can_sign)
       +        self.tx_hash_e.setText(tx_hash or _('Unknown'))
       +        if desc is None:
       +            self.tx_desc.hide()
       +        else:
       +            self.tx_desc.setText(_("Description") + ': ' + desc)
       +            self.tx_desc.show()
       +        self.status_label.setText(_('Status:') + ' ' + status)
       +
       +        if timestamp:
       +            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
       +            self.date_label.setText(_("Date: {}").format(time_str))
       +            self.date_label.show()
       +        elif exp_n:
       +            text = '%.2f MB'%(exp_n/1000000)
       +            self.date_label.setText(_('Position in mempool: {} from tip').format(text))
       +            self.date_label.show()
       +        else:
       +            self.date_label.hide()
       +        if amount is None:
       +            amount_str = _("Transaction unrelated to your wallet")
       +        elif amount > 0:
       +            amount_str = _("Amount received:") + ' %s'% format_amount(amount) + ' ' + base_unit
       +        else:
       +            amount_str = _("Amount sent:") + ' %s'% format_amount(-amount) + ' ' + base_unit
       +        size_str = _("Size:") + ' %d bytes'% size
       +        fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown'))
       +        if fee is not None:
       +            fee_rate = fee/size*1000
       +            fee_str += '  ( %s ) ' % self.main_window.format_fee_rate(fee_rate)
       +            confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
       +            if fee_rate > confirm_rate:
       +                fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!'
       +        self.amount_label.setText(amount_str)
       +        self.fee_label.setText(fee_str)
       +        self.size_label.setText(size_str)
       +        run_hook('transaction_dialog_update', self)
       +
       +    def add_io(self, vbox):
       +        if self.tx.locktime > 0:
       +            vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))
       +
       +        vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs())))
       +        ext = QTextCharFormat()
       +        rec = QTextCharFormat()
       +        rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True)))
       +        rec.setToolTip(_("Wallet receive address"))
       +        chg = QTextCharFormat()
       +        chg.setBackground(QBrush(ColorScheme.YELLOW.as_color(background=True)))
       +        chg.setToolTip(_("Wallet change address"))
       +        twofactor = QTextCharFormat()
       +        twofactor.setBackground(QBrush(ColorScheme.BLUE.as_color(background=True)))
       +        twofactor.setToolTip(_("TrustedCoin (2FA) fee for the next batch of transactions"))
       +
       +        def text_format(addr):
       +            if self.wallet.is_mine(addr):
       +                return chg if self.wallet.is_change(addr) else rec
       +            elif self.wallet.is_billing_address(addr):
       +                return twofactor
       +            return ext
       +
       +        def format_amount(amt):
       +            return self.main_window.format_amount(amt, whitespaces=True)
       +
       +        i_text = QTextEdit()
       +        i_text.setFont(QFont(MONOSPACE_FONT))
       +        i_text.setReadOnly(True)
       +        i_text.setMaximumHeight(100)
       +        cursor = i_text.textCursor()
       +        for x in self.tx.inputs():
       +            if x['type'] == 'coinbase':
       +                cursor.insertText('coinbase')
       +            else:
       +                prevout_hash = x.get('prevout_hash')
       +                prevout_n = x.get('prevout_n')
       +                cursor.insertText(prevout_hash + ":%-4d " % prevout_n, ext)
       +                addr = self.wallet.get_txin_address(x)
       +                if addr is None:
       +                    addr = ''
       +                cursor.insertText(addr, text_format(addr))
       +                if x.get('value'):
       +                    cursor.insertText(format_amount(x['value']), ext)
       +            cursor.insertBlock()
       +
       +        vbox.addWidget(i_text)
       +        vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
       +        o_text = QTextEdit()
       +        o_text.setFont(QFont(MONOSPACE_FONT))
       +        o_text.setReadOnly(True)
       +        o_text.setMaximumHeight(100)
       +        cursor = o_text.textCursor()
       +        for addr, v in self.tx.get_outputs():
       +            cursor.insertText(addr, text_format(addr))
       +            if v is not None:
       +                cursor.insertText('\t', ext)
       +                cursor.insertText(format_amount(v), ext)
       +            cursor.insertBlock()
       +        vbox.addWidget(o_text)
   DIR diff --git a/gui/qt/util.py b/electrum/gui/qt/util.py
   DIR diff --git a/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py
   DIR diff --git a/gui/stdio.py b/electrum/gui/stdio.py
   DIR diff --git a/electrum/gui/text.py b/electrum/gui/text.py
       t@@ -0,0 +1,503 @@
       +import tty, sys
       +import curses, datetime, locale
       +from decimal import Decimal
       +import getpass
       +
       +import electrum
       +from electrum.util import format_satoshis, set_verbosity
       +from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
       +from .. import Wallet, WalletStorage
       +
       +_ = lambda x:x
       +
       +
       +
       +class ElectrumGui:
       +
       +    def __init__(self, config, daemon, plugins):
       +
       +        self.config = config
       +        self.network = daemon.network
       +        storage = WalletStorage(config.get_wallet_path())
       +        if not storage.file_exists():
       +            print("Wallet not found. try 'electrum create'")
       +            exit()
       +        if storage.is_encrypted():
       +            password = getpass.getpass('Password:', stream=None)
       +            storage.decrypt(password)
       +        self.wallet = Wallet(storage)
       +        self.wallet.start_threads(self.network)
       +        self.contacts = self.wallet.contacts
       +
       +        locale.setlocale(locale.LC_ALL, '')
       +        self.encoding = locale.getpreferredencoding()
       +
       +        self.stdscr = curses.initscr()
       +        curses.noecho()
       +        curses.cbreak()
       +        curses.start_color()
       +        curses.use_default_colors()
       +        curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
       +        curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN)
       +        curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)
       +        self.stdscr.keypad(1)
       +        self.stdscr.border(0)
       +        self.maxy, self.maxx = self.stdscr.getmaxyx()
       +        self.set_cursor(0)
       +        self.w = curses.newwin(10, 50, 5, 5)
       +
       +        set_verbosity(False)
       +        self.tab = 0
       +        self.pos = 0
       +        self.popup_pos = 0
       +
       +        self.str_recipient = ""
       +        self.str_description = ""
       +        self.str_amount = ""
       +        self.str_fee = ""
       +        self.history = None
       +
       +        if self.network:
       +            self.network.register_callback(self.update, ['updated'])
       +
       +        self.tab_names = [_("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner")]
       +        self.num_tabs = len(self.tab_names)
       +
       +
       +    def set_cursor(self, x):
       +        try:
       +            curses.curs_set(x)
       +        except Exception:
       +            pass
       +
       +    def restore_or_create(self):
       +        pass
       +
       +    def verify_seed(self):
       +        pass
       +
       +    def get_string(self, y, x):
       +        self.set_cursor(1)
       +        curses.echo()
       +        self.stdscr.addstr( y, x, " "*20, curses.A_REVERSE)
       +        s = self.stdscr.getstr(y,x)
       +        curses.noecho()
       +        self.set_cursor(0)
       +        return s
       +
       +    def update(self, event):
       +        self.update_history()
       +        if self.tab == 0:
       +            self.print_history()
       +        self.refresh()
       +
       +    def print_history(self):
       +
       +        width = [20, 40, 14, 14]
       +        delta = (self.maxx - sum(width) - 4)/3
       +        format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%"+"%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
       +
       +        if self.history is None:
       +            self.update_history()
       +
       +        self.print_list(self.history[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))
       +
       +    def update_history(self):
       +        width = [20, 40, 14, 14]
       +        delta = (self.maxx - sum(width) - 4)/3
       +        format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%"+"%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
       +
       +        b = 0
       +        self.history = []
       +        for item in self.wallet.get_history():
       +            tx_hash, height, conf, timestamp, value, balance = item
       +            if conf:
       +                try:
       +                    time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
       +                except Exception:
       +                    time_str = "------"
       +            else:
       +                time_str = 'unconfirmed'
       +
       +            label = self.wallet.get_label(tx_hash)
       +            if len(label) > 40:
       +                label = label[0:37] + '...'
       +            self.history.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
       +
       +
       +    def print_balance(self):
       +        if not self.network:
       +            msg = _("Offline")
       +        elif self.network.is_connected():
       +            if not self.wallet.up_to_date:
       +                msg = _("Synchronizing...")
       +            else:
       +                c, u, x =  self.wallet.get_balance()
       +                msg = _("Balance")+": %f  "%(Decimal(c) / COIN)
       +                if u:
       +                    msg += "  [%f unconfirmed]"%(Decimal(u) / COIN)
       +                if x:
       +                    msg += "  [%f unmatured]"%(Decimal(x) / COIN)
       +        else:
       +            msg = _("Not connected")
       +
       +        self.stdscr.addstr( self.maxy -1, 3, msg)
       +
       +        for i in range(self.num_tabs):
       +            self.stdscr.addstr( 0, 2 + 2*i + len(''.join(self.tab_names[0:i])), ' '+self.tab_names[i]+' ', curses.A_BOLD if self.tab == i else 0)
       +
       +        self.stdscr.addstr(self.maxy -1, self.maxx-30, ' '.join([_("Settings"), _("Network"), _("Quit")]))
       +
       +    def print_receive(self):
       +        addr = self.wallet.get_receiving_address()
       +        self.stdscr.addstr(2, 1, "Address: "+addr)
       +        self.print_qr(addr)
       +
       +    def print_contacts(self):
       +        messages = map(lambda x: "%20s   %45s "%(x[0], x[1][1]), self.contacts.items())
       +        self.print_list(messages, "%19s  %15s "%("Key", "Value"))
       +
       +    def print_addresses(self):
       +        fmt = "%-35s  %-30s"
       +        messages = map(lambda addr: fmt % (addr, self.wallet.labels.get(addr,"")), self.wallet.get_addresses())
       +        self.print_list(messages,   fmt % ("Address", "Label"))
       +
       +    def print_edit_line(self, y, label, text, index, size):
       +        text += " "*(size - len(text) )
       +        self.stdscr.addstr( y, 2, label)
       +        self.stdscr.addstr( y, 15, text, curses.A_REVERSE if self.pos%6==index else curses.color_pair(1))
       +
       +    def print_send_tab(self):
       +        self.stdscr.clear()
       +        self.print_edit_line(3, _("Pay to"), self.str_recipient, 0, 40)
       +        self.print_edit_line(5, _("Description"), self.str_description, 1, 40)
       +        self.print_edit_line(7, _("Amount"), self.str_amount, 2, 15)
       +        self.print_edit_line(9, _("Fee"), self.str_fee, 3, 15)
       +        self.stdscr.addstr( 12, 15, _("[Send]"), curses.A_REVERSE if self.pos%6==4 else curses.color_pair(2))
       +        self.stdscr.addstr( 12, 25, _("[Clear]"), curses.A_REVERSE if self.pos%6==5 else curses.color_pair(2))
       +        self.maxpos = 6
       +
       +    def print_banner(self):
       +        if self.network:
       +            self.print_list( self.network.banner.split('\n'))
       +
       +    def print_qr(self, data):
       +        import qrcode
       +        try:
       +            from StringIO import StringIO
       +        except ImportError:
       +            from io import StringIO
       +
       +        s = StringIO()
       +        self.qr = qrcode.QRCode()
       +        self.qr.add_data(data)
       +        self.qr.print_ascii(out=s, invert=False)
       +        msg = s.getvalue()
       +        lines = msg.split('\n')
       +        for i, l in enumerate(lines):
       +            l = l.encode("utf-8")
       +            self.stdscr.addstr(i+5, 5, l, curses.color_pair(3))
       +
       +    def print_list(self, lst, firstline = None):
       +        lst = list(lst)
       +        self.maxpos = len(lst)
       +        if not self.maxpos: return
       +        if firstline:
       +            firstline += " "*(self.maxx -2 - len(firstline))
       +            self.stdscr.addstr( 1, 1, firstline )
       +        for i in range(self.maxy-4):
       +            msg = lst[i] if i < len(lst) else ""
       +            msg += " "*(self.maxx - 2 - len(msg))
       +            m = msg[0:self.maxx - 2]
       +            m = m.encode(self.encoding)
       +            self.stdscr.addstr( i+2, 1, m, curses.A_REVERSE if i == (self.pos % self.maxpos) else 0)
       +
       +    def refresh(self):
       +        if self.tab == -1: return
       +        self.stdscr.border(0)
       +        self.print_balance()
       +        self.stdscr.refresh()
       +
       +    def main_command(self):
       +        c = self.stdscr.getch()
       +        print(c)
       +        cc = curses.unctrl(c).decode()
       +        if   c == curses.KEY_RIGHT: self.tab = (self.tab + 1)%self.num_tabs
       +        elif c == curses.KEY_LEFT: self.tab = (self.tab - 1)%self.num_tabs
       +        elif c == curses.KEY_DOWN: self.pos +=1
       +        elif c == curses.KEY_UP: self.pos -= 1
       +        elif c == 9: self.pos +=1 # tab
       +        elif cc in ['^W', '^C', '^X', '^Q']: self.tab = -1
       +        elif cc in ['^N']: self.network_dialog()
       +        elif cc == '^S': self.settings_dialog()
       +        else: return c
       +        if self.pos<0: self.pos=0
       +        if self.pos>=self.maxpos: self.pos=self.maxpos - 1
       +
       +    def run_tab(self, i, print_func, exec_func):
       +        while self.tab == i:
       +            self.stdscr.clear()
       +            print_func()
       +            self.refresh()
       +            c = self.main_command()
       +            if c: exec_func(c)
       +
       +
       +    def run_history_tab(self, c):
       +        if c == 10:
       +            out = self.run_popup('',["blah","foo"])
       +
       +
       +    def edit_str(self, target, c, is_num=False):
       +        # detect backspace
       +        cc = curses.unctrl(c).decode()
       +        if c in [8, 127, 263] and target:
       +            target = target[:-1]
       +        elif not is_num or cc in '0123456789.':
       +            target += cc
       +        return target
       +
       +
       +    def run_send_tab(self, c):
       +        if self.pos%6 == 0:
       +            self.str_recipient = self.edit_str(self.str_recipient, c)
       +        if self.pos%6 == 1:
       +            self.str_description = self.edit_str(self.str_description, c)
       +        if self.pos%6 == 2:
       +            self.str_amount = self.edit_str(self.str_amount, c, True)
       +        elif self.pos%6 == 3:
       +            self.str_fee = self.edit_str(self.str_fee, c, True)
       +        elif self.pos%6==4:
       +            if c == 10: self.do_send()
       +        elif self.pos%6==5:
       +            if c == 10: self.do_clear()
       +
       +
       +    def run_receive_tab(self, c):
       +        if c == 10:
       +            out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"])
       +
       +    def run_contacts_tab(self, c):
       +        if c == 10 and self.contacts:
       +            out = self.run_popup('Address', ["Copy", "Pay to", "Edit label", "Delete"]).get('button')
       +            key = list(self.contacts.keys())[self.pos%len(self.contacts.keys())]
       +            if out == "Pay to":
       +                self.tab = 1
       +                self.str_recipient = key
       +                self.pos = 2
       +            elif out == "Edit label":
       +                s = self.get_string(6 + self.pos, 18)
       +                if s:
       +                    self.wallet.labels[key] = s
       +
       +    def run_banner_tab(self, c):
       +        self.show_message(repr(c))
       +        pass
       +
       +    def main(self):
       +
       +        tty.setraw(sys.stdin)
       +        while self.tab != -1:
       +            self.run_tab(0, self.print_history, self.run_history_tab)
       +            self.run_tab(1, self.print_send_tab, self.run_send_tab)
       +            self.run_tab(2, self.print_receive, self.run_receive_tab)
       +            self.run_tab(3, self.print_addresses, self.run_banner_tab)
       +            self.run_tab(4, self.print_contacts, self.run_contacts_tab)
       +            self.run_tab(5, self.print_banner, self.run_banner_tab)
       +
       +        tty.setcbreak(sys.stdin)
       +        curses.nocbreak()
       +        self.stdscr.keypad(0)
       +        curses.echo()
       +        curses.endwin()
       +
       +
       +    def do_clear(self):
       +        self.str_amount = ''
       +        self.str_recipient = ''
       +        self.str_fee = ''
       +        self.str_description = ''
       +
       +    def do_send(self):
       +        if not is_address(self.str_recipient):
       +            self.show_message(_('Invalid Bitcoin address'))
       +            return
       +        try:
       +            amount = int(Decimal(self.str_amount) * COIN)
       +        except Exception:
       +            self.show_message(_('Invalid Amount'))
       +            return
       +        try:
       +            fee = int(Decimal(self.str_fee) * COIN)
       +        except Exception:
       +            self.show_message(_('Invalid Fee'))
       +            return
       +
       +        if self.wallet.has_password():
       +            password = self.password_dialog()
       +            if not password:
       +                return
       +        else:
       +            password = None
       +        try:
       +            tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee)
       +        except Exception as e:
       +            self.show_message(str(e))
       +            return
       +
       +        if self.str_description:
       +            self.wallet.labels[tx.txid()] = self.str_description
       +
       +        self.show_message(_("Please wait..."), getchar=False)
       +        status, msg = self.network.broadcast_transaction(tx)
       +
       +        if status:
       +            self.show_message(_('Payment sent.'))
       +            self.do_clear()
       +            #self.update_contacts_tab()
       +        else:
       +            self.show_message(_('Error'))
       +
       +
       +    def show_message(self, message, getchar = True):
       +        w = self.w
       +        w.clear()
       +        w.border(0)
       +        for i, line in enumerate(message.split('\n')):
       +            w.addstr(2+i,2,line)
       +        w.refresh()
       +        if getchar: c = self.stdscr.getch()
       +
       +    def run_popup(self, title, items):
       +        return self.run_dialog(title, list(map(lambda x: {'type':'button','label':x}, items)), interval=1, y_pos = self.pos+3)
       +
       +    def network_dialog(self):
       +        if not self.network:
       +            return
       +        params = self.network.get_parameters()
       +        host, port, protocol, proxy_config, auto_connect = params
       +        srv = 'auto-connect' if auto_connect else self.network.default_server
       +        out = self.run_dialog('Network', [
       +            {'label':'server', 'type':'str', 'value':srv},
       +            {'label':'proxy', 'type':'str', 'value':self.config.get('proxy', '')},
       +            ], buttons = 1)
       +        if out:
       +            if out.get('server'):
       +                server = out.get('server')
       +                auto_connect = server == 'auto-connect'
       +                if not auto_connect:
       +                    try:
       +                        host, port, protocol = server.split(':')
       +                    except Exception:
       +                        self.show_message("Error:" + server + "\nIn doubt, type \"auto-connect\"")
       +                        return False
       +            if out.get('server') or out.get('proxy'):
       +                proxy = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config
       +                self.network.set_parameters(host, port, protocol, proxy, auto_connect)
       +
       +    def settings_dialog(self):
       +        fee = str(Decimal(self.config.fee_per_kb()) / COIN)
       +        out = self.run_dialog('Settings', [
       +            {'label':'Default fee', 'type':'satoshis', 'value': fee }
       +            ], buttons = 1)
       +        if out:
       +            if out.get('Default fee'):
       +                fee = int(Decimal(out['Default fee']) * COIN)
       +                self.config.set_key('fee_per_kb', fee, True)
       +
       +
       +    def password_dialog(self):
       +        out = self.run_dialog('Password', [
       +            {'label':'Password', 'type':'password', 'value':''}
       +            ], buttons = 1)
       +        return out.get('Password')
       +
       +
       +    def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3):
       +        self.popup_pos = 0
       +
       +        self.w = curses.newwin( 5 + len(list(items))*interval + (2 if buttons else 0), 50, y_pos, 5)
       +        w = self.w
       +        out = {}
       +        while True:
       +            w.clear()
       +            w.border(0)
       +            w.addstr( 0, 2, title)
       +
       +            num = len(list(items))
       +
       +            numpos = num
       +            if buttons: numpos += 2
       +
       +            for i in range(num):
       +                item = items[i]
       +                label = item.get('label')
       +                if item.get('type') == 'list':
       +                    value = item.get('value','')
       +                elif item.get('type') == 'satoshis':
       +                    value = item.get('value','')
       +                elif item.get('type') == 'str':
       +                    value = item.get('value','')
       +                elif item.get('type') == 'password':
       +                    value = '*'*len(item.get('value',''))
       +                else:
       +                    value = ''
       +                if value is None:
       +                    value = ''
       +                if len(value)<20:
       +                    value += ' '*(20-len(value))
       +
       +                if 'value' in item:
       +                    w.addstr( 2+interval*i, 2, label)
       +                    w.addstr( 2+interval*i, 15, value, curses.A_REVERSE if self.popup_pos%numpos==i else curses.color_pair(1) )
       +                else:
       +                    w.addstr( 2+interval*i, 2, label, curses.A_REVERSE if self.popup_pos%numpos==i else 0)
       +
       +            if buttons:
       +                w.addstr( 5+interval*i, 10, "[  ok  ]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-2) else curses.color_pair(2))
       +                w.addstr( 5+interval*i, 25, "[cancel]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-1) else curses.color_pair(2))
       +
       +            w.refresh()
       +
       +            c = self.stdscr.getch()
       +            if c in [ord('q'), 27]: break
       +            elif c in [curses.KEY_LEFT, curses.KEY_UP]: self.popup_pos -= 1
       +            elif c in [curses.KEY_RIGHT, curses.KEY_DOWN]: self.popup_pos +=1
       +            else:
       +                i = self.popup_pos%numpos
       +                if buttons and c==10:
       +                    if i == numpos-2:
       +                        return out
       +                    elif i == numpos -1:
       +                        return {}
       +
       +                item = items[i]
       +                _type = item.get('type')
       +
       +                if _type == 'str':
       +                    item['value'] = self.edit_str(item['value'], c)
       +                    out[item.get('label')] = item.get('value')
       +
       +                elif _type == 'password':
       +                    item['value'] = self.edit_str(item['value'], c)
       +                    out[item.get('label')] = item ['value']
       +
       +                elif _type == 'satoshis':
       +                    item['value'] = self.edit_str(item['value'], c, True)
       +                    out[item.get('label')] = item.get('value')
       +
       +                elif _type == 'list':
       +                    choices = item.get('choices')
       +                    try:
       +                        j = choices.index(item.get('value'))
       +                    except Exception:
       +                        j = 0
       +                    new_choice = choices[(j + 1)% len(choices)]
       +                    item['value'] = new_choice
       +                    out[item.get('label')] = item.get('value')
       +
       +                elif _type == 'button':
       +                    out['button'] = item.get('label')
       +                    break
       +
       +        return out
   DIR diff --git a/electrum/i18n.py b/electrum/i18n.py
       t@@ -0,0 +1,81 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2012 thomasv@gitorious
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import os
       +
       +import gettext
       +
       +LOCALE_DIR = os.path.join(os.path.dirname(__file__), 'locale')
       +language = gettext.translation('electrum', LOCALE_DIR, fallback=True)
       +
       +
       +def _(x):
       +    global language
       +    return language.gettext(x)
       +
       +
       +def set_language(x):
       +    global language
       +    if x:
       +        language = gettext.translation('electrum', LOCALE_DIR, fallback=True, languages=[x])
       +
       +
       +languages = {
       +    '': _('Default'),
       +    'ar_SA': _('Arabic'),
       +    'bg_BG': _('Bulgarian'),
       +    'cs_CZ': _('Czech'),
       +    'da_DK': _('Danish'),
       +    'de_DE': _('German'),
       +    'el_GR': _('Greek'),
       +    'eo_UY': _('Esperanto'),
       +    'en_UK': _('English'),
       +    'es_ES': _('Spanish'),
       +    'fa_IR': _('Persian'),
       +    'fr_FR': _('French'),
       +    'hu_HU': _('Hungarian'),
       +    'hy_AM': _('Armenian'),
       +    'id_ID': _('Indonesian'),
       +    'it_IT': _('Italian'),
       +    'ja_JP': _('Japanese'),
       +    'ky_KG': _('Kyrgyz'),
       +    'lv_LV': _('Latvian'),
       +    'nb_NO': _('Norwegian Bokmal'),
       +    'nl_NL': _('Dutch'),
       +    'pl_PL': _('Polish'),
       +    'pt_BR': _('Brasilian'),
       +    'pt_PT': _('Portuguese'),
       +    'ro_RO': _('Romanian'),
       +    'ru_RU': _('Russian'),
       +    'sk_SK': _('Slovak'),
       +    'sl_SI': _('Slovenian'),
       +    'sv_SE': _('Swedish'),
       +    'ta_IN': _('Tamil'),
       +    'th_TH': _('Thai'),
       +    'tr_TR': _('Turkish'),
       +    'uk_UA': _('Ukrainian'),
       +    'vi_VN': _('Vietnamese'),
       +    'zh_CN': _('Chinese Simplified'),
       +    'zh_TW': _('Chinese Traditional')
       +}
   DIR diff --git a/electrum/interface.py b/electrum/interface.py
       t@@ -0,0 +1,407 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2011 thomasv@gitorious
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import os
       +import re
       +import socket
       +import ssl
       +import sys
       +import threading
       +import time
       +import traceback
       +
       +import requests
       +
       +from .util import print_error
       +
       +ca_path = requests.certs.where()
       +
       +from . import util
       +from . import x509
       +from . import pem
       +
       +
       +def Connection(server, queue, config_path):
       +    """Makes asynchronous connections to a remote Electrum server.
       +    Returns the running thread that is making the connection.
       +
       +    Once the thread has connected, it finishes, placing a tuple on the
       +    queue of the form (server, socket), where socket is None if
       +    connection failed.
       +    """
       +    host, port, protocol = server.rsplit(':', 2)
       +    if not protocol in 'st':
       +        raise Exception('Unknown protocol: %s' % protocol)
       +    c = TcpConnection(server, queue, config_path)
       +    c.start()
       +    return c
       +
       +
       +class TcpConnection(threading.Thread, util.PrintError):
       +
       +    def __init__(self, server, queue, config_path):
       +        threading.Thread.__init__(self)
       +        self.config_path = config_path
       +        self.queue = queue
       +        self.server = server
       +        self.host, self.port, self.protocol = self.server.rsplit(':', 2)
       +        self.host = str(self.host)
       +        self.port = int(self.port)
       +        self.use_ssl = (self.protocol == 's')
       +        self.daemon = True
       +
       +    def diagnostic_name(self):
       +        return self.host
       +
       +    def check_host_name(self, peercert, name):
       +        """Simple certificate/host name checker.  Returns True if the
       +        certificate matches, False otherwise.  Does not support
       +        wildcards."""
       +        # Check that the peer has supplied a certificate.
       +        # None/{} is not acceptable.
       +        if not peercert:
       +            return False
       +        if 'subjectAltName' in peercert:
       +            for typ, val in peercert["subjectAltName"]:
       +                if typ == "DNS" and val == name:
       +                    return True
       +        else:
       +            # Only check the subject DN if there is no subject alternative
       +            # name.
       +            cn = None
       +            for attr, val in peercert["subject"]:
       +                # Use most-specific (last) commonName attribute.
       +                if attr == "commonName":
       +                    cn = val
       +            if cn is not None:
       +                return cn == name
       +        return False
       +
       +    def get_simple_socket(self):
       +        try:
       +            l = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM)
       +        except socket.gaierror:
       +            self.print_error("cannot resolve hostname")
       +            return
       +        e = None
       +        for res in l:
       +            try:
       +                s = socket.socket(res[0], socket.SOCK_STREAM)
       +                s.settimeout(10)
       +                s.connect(res[4])
       +                s.settimeout(2)
       +                s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
       +                return s
       +            except BaseException as _e:
       +                e = _e
       +                continue
       +        else:
       +            self.print_error("failed to connect", str(e))
       +
       +    @staticmethod
       +    def get_ssl_context(cert_reqs, ca_certs):
       +        context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_certs)
       +        context.check_hostname = False
       +        context.verify_mode = cert_reqs
       +
       +        context.options |= ssl.OP_NO_SSLv2
       +        context.options |= ssl.OP_NO_SSLv3
       +        context.options |= ssl.OP_NO_TLSv1
       +
       +        return context
       +
       +    def get_socket(self):
       +        if self.use_ssl:
       +            cert_path = os.path.join(self.config_path, 'certs', self.host)
       +            if not os.path.exists(cert_path):
       +                is_new = True
       +                s = self.get_simple_socket()
       +                if s is None:
       +                    return
       +                # try with CA first
       +                try:
       +                    context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_path)
       +                    s = context.wrap_socket(s, do_handshake_on_connect=True)
       +                except ssl.SSLError as e:
       +                    self.print_error(e)
       +                except:
       +                    return
       +                else:
       +                    try:
       +                        peer_cert = s.getpeercert()
       +                    except OSError:
       +                        return
       +                    if self.check_host_name(peer_cert, self.host):
       +                        self.print_error("SSL certificate signed by CA")
       +                        return s
       +                # get server certificate.
       +                # Do not use ssl.get_server_certificate because it does not work with proxy
       +                s = self.get_simple_socket()
       +                if s is None:
       +                    return
       +                try:
       +                    context = self.get_ssl_context(cert_reqs=ssl.CERT_NONE, ca_certs=None)
       +                    s = context.wrap_socket(s)
       +                except ssl.SSLError as e:
       +                    self.print_error("SSL error retrieving SSL certificate:", e)
       +                    return
       +                except:
       +                    return
       +
       +                try:
       +                    dercert = s.getpeercert(True)
       +                except OSError:
       +                    return
       +                s.close()
       +                cert = ssl.DER_cert_to_PEM_cert(dercert)
       +                # workaround android bug
       +                cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert)
       +                temporary_path = cert_path + '.temp'
       +                util.assert_datadir_available(self.config_path)
       +                with open(temporary_path, "w", encoding='utf-8') as f:
       +                    f.write(cert)
       +                    f.flush()
       +                    os.fsync(f.fileno())
       +            else:
       +                is_new = False
       +
       +        s = self.get_simple_socket()
       +        if s is None:
       +            return
       +
       +        if self.use_ssl:
       +            try:
       +                context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED,
       +                                               ca_certs=(temporary_path if is_new else cert_path))
       +                s = context.wrap_socket(s, do_handshake_on_connect=True)
       +            except socket.timeout:
       +                self.print_error('timeout')
       +                return
       +            except ssl.SSLError as e:
       +                self.print_error("SSL error:", e)
       +                if e.errno != 1:
       +                    return
       +                if is_new:
       +                    rej = cert_path + '.rej'
       +                    if os.path.exists(rej):
       +                        os.unlink(rej)
       +                    os.rename(temporary_path, rej)
       +                else:
       +                    util.assert_datadir_available(self.config_path)
       +                    with open(cert_path, encoding='utf-8') as f:
       +                        cert = f.read()
       +                    try:
       +                        b = pem.dePem(cert, 'CERTIFICATE')
       +                        x = x509.X509(b)
       +                    except:
       +                        traceback.print_exc(file=sys.stderr)
       +                        self.print_error("wrong certificate")
       +                        return
       +                    try:
       +                        x.check_date()
       +                    except:
       +                        self.print_error("certificate has expired:", cert_path)
       +                        os.unlink(cert_path)
       +                        return
       +                    self.print_error("wrong certificate")
       +                if e.errno == 104:
       +                    return
       +                return
       +            except BaseException as e:
       +                self.print_error(e)
       +                traceback.print_exc(file=sys.stderr)
       +                return
       +
       +            if is_new:
       +                self.print_error("saving certificate")
       +                os.rename(temporary_path, cert_path)
       +
       +        return s
       +
       +    def run(self):
       +        socket = self.get_socket()
       +        if socket:
       +            self.print_error("connected")
       +        self.queue.put((self.server, socket))
       +
       +
       +class Interface(util.PrintError):
       +    """The Interface class handles a socket connected to a single remote
       +    Electrum server.  Its exposed API is:
       +
       +    - Member functions close(), fileno(), get_responses(), has_timed_out(),
       +      ping_required(), queue_request(), send_requests()
       +    - Member variable server.
       +    """
       +
       +    def __init__(self, server, socket):
       +        self.server = server
       +        self.host, _, _ = server.rsplit(':', 2)
       +        self.socket = socket
       +
       +        self.pipe = util.SocketPipe(socket)
       +        self.pipe.set_timeout(0.0)  # Don't wait for data
       +        # Dump network messages.  Set at runtime from the console.
       +        self.debug = False
       +        self.unsent_requests = []
       +        self.unanswered_requests = {}
       +        self.last_send = time.time()
       +        self.closed_remotely = False
       +
       +    def diagnostic_name(self):
       +        return self.host
       +
       +    def fileno(self):
       +        # Needed for select
       +        return self.socket.fileno()
       +
       +    def close(self):
       +        if not self.closed_remotely:
       +            try:
       +                self.socket.shutdown(socket.SHUT_RDWR)
       +            except socket.error:
       +                pass
       +        self.socket.close()
       +
       +    def queue_request(self, *args):  # method, params, _id
       +        '''Queue a request, later to be send with send_requests when the
       +        socket is available for writing.
       +        '''
       +        self.request_time = time.time()
       +        self.unsent_requests.append(args)
       +
       +    def num_requests(self):
       +        '''Keep unanswered requests below 100'''
       +        n = 100 - len(self.unanswered_requests)
       +        return min(n, len(self.unsent_requests))
       +
       +    def send_requests(self):
       +        '''Sends queued requests.  Returns False on failure.'''
       +        self.last_send = time.time()
       +        make_dict = lambda m, p, i: {'method': m, 'params': p, 'id': i}
       +        n = self.num_requests()
       +        wire_requests = self.unsent_requests[0:n]
       +        try:
       +            self.pipe.send_all([make_dict(*r) for r in wire_requests])
       +        except BaseException as e:
       +            self.print_error("pipe send error:", e)
       +            return False
       +        self.unsent_requests = self.unsent_requests[n:]
       +        for request in wire_requests:
       +            if self.debug:
       +                self.print_error("-->", request)
       +            self.unanswered_requests[request[2]] = request
       +        return True
       +
       +    def ping_required(self):
       +        '''Returns True if a ping should be sent.'''
       +        return time.time() - self.last_send > 300
       +
       +    def has_timed_out(self):
       +        '''Returns True if the interface has timed out.'''
       +        if (self.unanswered_requests and time.time() - self.request_time > 10
       +            and self.pipe.idle_time() > 10):
       +            self.print_error("timeout", len(self.unanswered_requests))
       +            return True
       +
       +        return False
       +
       +    def get_responses(self):
       +        '''Call if there is data available on the socket.  Returns a list of
       +        (request, response) pairs.  Notifications are singleton
       +        unsolicited responses presumably as a result of prior
       +        subscriptions, so request is None and there is no 'id' member.
       +        Otherwise it is a response, which has an 'id' member and a
       +        corresponding request.  If the connection was closed remotely
       +        or the remote server is misbehaving, a (None, None) will appear.
       +        '''
       +        responses = []
       +        while True:
       +            try:
       +                response = self.pipe.get()
       +            except util.timeout:
       +                break
       +            if not type(response) is dict:
       +                responses.append((None, None))
       +                if response is None:
       +                    self.closed_remotely = True
       +                    self.print_error("connection closed remotely")
       +                break
       +            if self.debug:
       +                self.print_error("<--", response)
       +            wire_id = response.get('id', None)
       +            if wire_id is None:  # Notification
       +                responses.append((None, response))
       +            else:
       +                request = self.unanswered_requests.pop(wire_id, None)
       +                if request:
       +                    responses.append((request, response))
       +                else:
       +                    self.print_error("unknown wire ID", wire_id)
       +                    responses.append((None, None)) # Signal
       +                    break
       +
       +        return responses
       +
       +
       +def check_cert(host, cert):
       +    try:
       +        b = pem.dePem(cert, 'CERTIFICATE')
       +        x = x509.X509(b)
       +    except:
       +        traceback.print_exc(file=sys.stdout)
       +        return
       +
       +    try:
       +        x.check_date()
       +        expired = False
       +    except:
       +        expired = True
       +
       +    m = "host: %s\n"%host
       +    m += "has_expired: %s\n"% expired
       +    util.print_msg(m)
       +
       +
       +# Used by tests
       +def _match_hostname(name, val):
       +    if val == name:
       +        return True
       +
       +    return val.startswith('*.') and name.endswith(val[1:])
       +
       +
       +def test_certificates():
       +    from .simple_config import SimpleConfig
       +    config = SimpleConfig()
       +    mydir = os.path.join(config.path, "certs")
       +    certs = os.listdir(mydir)
       +    for c in certs:
       +        p = os.path.join(mydir,c)
       +        with open(p, encoding='utf-8') as f:
       +            cert = f.read()
       +        check_cert(c, cert)
       +
       +if __name__ == "__main__":
       +    test_certificates()
   DIR diff --git a/electrum/jsonrpc.py b/electrum/jsonrpc.py
       t@@ -0,0 +1,98 @@
       +#!/usr/bin/env python3
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2018 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler
       +from base64 import b64decode
       +import time
       +
       +from . import util
       +
       +
       +class RPCAuthCredentialsInvalid(Exception):
       +    def __str__(self):
       +        return 'Authentication failed (bad credentials)'
       +
       +
       +class RPCAuthCredentialsMissing(Exception):
       +    def __str__(self):
       +        return 'Authentication failed (missing credentials)'
       +
       +
       +class RPCAuthUnsupportedType(Exception):
       +    def __str__(self):
       +        return 'Authentication failed (only basic auth is supported)'
       +
       +
       +# based on http://acooke.org/cute/BasicHTTPA0.html by andrew cooke
       +class VerifyingJSONRPCServer(SimpleJSONRPCServer):
       +
       +    def __init__(self, *args, rpc_user, rpc_password, **kargs):
       +
       +        self.rpc_user = rpc_user
       +        self.rpc_password = rpc_password
       +
       +        class VerifyingRequestHandler(SimpleJSONRPCRequestHandler):
       +            def parse_request(myself):
       +                # first, call the original implementation which returns
       +                # True if all OK so far
       +                if SimpleJSONRPCRequestHandler.parse_request(myself):
       +                    # Do not authenticate OPTIONS-requests
       +                    if myself.command.strip() == 'OPTIONS':
       +                        return True
       +                    try:
       +                        self.authenticate(myself.headers)
       +                        return True
       +                    except (RPCAuthCredentialsInvalid, RPCAuthCredentialsMissing,
       +                            RPCAuthUnsupportedType) as e:
       +                        myself.send_error(401, str(e))
       +                    except BaseException as e:
       +                        import traceback, sys
       +                        traceback.print_exc(file=sys.stderr)
       +                        myself.send_error(500, str(e))
       +                return False
       +
       +        SimpleJSONRPCServer.__init__(
       +            self, requestHandler=VerifyingRequestHandler, *args, **kargs)
       +
       +    def authenticate(self, headers):
       +        if self.rpc_password == '':
       +            # RPC authentication is disabled
       +            return
       +
       +        auth_string = headers.get('Authorization', None)
       +        if auth_string is None:
       +            raise RPCAuthCredentialsMissing()
       +
       +        (basic, _, encoded) = auth_string.partition(' ')
       +        if basic != 'Basic':
       +            raise RPCAuthUnsupportedType()
       +
       +        encoded = util.to_bytes(encoded, 'utf8')
       +        credentials = util.to_string(b64decode(encoded), 'utf8')
       +        (username, _, password) = credentials.partition(':')
       +        if not (util.constant_time_compare(username, self.rpc_user)
       +                and util.constant_time_compare(password, self.rpc_password)):
       +            time.sleep(0.050)
       +            raise RPCAuthCredentialsInvalid()
   DIR diff --git a/electrum/keystore.py b/electrum/keystore.py
       t@@ -0,0 +1,798 @@
       +#!/usr/bin/env python2
       +# -*- mode: python -*-
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2016  The Electrum developers
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from unicodedata import normalize
       +
       +from . import bitcoin, ecc, constants
       +from .bitcoin import *
       +from .ecc import string_to_number, number_to_string
       +from .crypto import pw_decode, pw_encode
       +from .util import (PrintError, InvalidPassword, hfu, WalletFileException,
       +                   BitcoinException)
       +from .mnemonic import Mnemonic, load_wordlist
       +from .plugin import run_hook
       +
       +
       +class KeyStore(PrintError):
       +
       +    def has_seed(self):
       +        return False
       +
       +    def is_watching_only(self):
       +        return False
       +
       +    def can_import(self):
       +        return False
       +
       +    def may_have_password(self):
       +        """Returns whether the keystore can be encrypted with a password."""
       +        raise NotImplementedError()
       +
       +    def get_tx_derivations(self, tx):
       +        keypairs = {}
       +        for txin in tx.inputs():
       +            num_sig = txin.get('num_sig')
       +            if num_sig is None:
       +                continue
       +            x_signatures = txin['signatures']
       +            signatures = [sig for sig in x_signatures if sig]
       +            if len(signatures) == num_sig:
       +                # input is complete
       +                continue
       +            for k, x_pubkey in enumerate(txin['x_pubkeys']):
       +                if x_signatures[k] is not None:
       +                    # this pubkey already signed
       +                    continue
       +                derivation = self.get_pubkey_derivation(x_pubkey)
       +                if not derivation:
       +                    continue
       +                keypairs[x_pubkey] = derivation
       +        return keypairs
       +
       +    def can_sign(self, tx):
       +        if self.is_watching_only():
       +            return False
       +        return bool(self.get_tx_derivations(tx))
       +
       +    def ready_to_sign(self):
       +        return not self.is_watching_only()
       +
       +
       +class Software_KeyStore(KeyStore):
       +
       +    def __init__(self):
       +        KeyStore.__init__(self)
       +
       +    def may_have_password(self):
       +        return not self.is_watching_only()
       +
       +    def sign_message(self, sequence, message, password):
       +        privkey, compressed = self.get_private_key(sequence, password)
       +        key = ecc.ECPrivkey(privkey)
       +        return key.sign_message(message, compressed)
       +
       +    def decrypt_message(self, sequence, message, password):
       +        privkey, compressed = self.get_private_key(sequence, password)
       +        ec = ecc.ECPrivkey(privkey)
       +        decrypted = ec.decrypt_message(message)
       +        return decrypted
       +
       +    def sign_transaction(self, tx, password):
       +        if self.is_watching_only():
       +            return
       +        # Raise if password is not correct.
       +        self.check_password(password)
       +        # Add private keys
       +        keypairs = self.get_tx_derivations(tx)
       +        for k, v in keypairs.items():
       +            keypairs[k] = self.get_private_key(v, password)
       +        # Sign
       +        if keypairs:
       +            tx.sign(keypairs)
       +
       +
       +class Imported_KeyStore(Software_KeyStore):
       +    # keystore for imported private keys
       +
       +    def __init__(self, d):
       +        Software_KeyStore.__init__(self)
       +        self.keypairs = d.get('keypairs', {})
       +
       +    def is_deterministic(self):
       +        return False
       +
       +    def get_master_public_key(self):
       +        return None
       +
       +    def dump(self):
       +        return {
       +            'type': 'imported',
       +            'keypairs': self.keypairs,
       +        }
       +
       +    def can_import(self):
       +        return True
       +
       +    def check_password(self, password):
       +        pubkey = list(self.keypairs.keys())[0]
       +        self.get_private_key(pubkey, password)
       +
       +    def import_privkey(self, sec, password):
       +        txin_type, privkey, compressed = deserialize_privkey(sec)
       +        pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
       +        # re-serialize the key so the internal storage format is consistent
       +        serialized_privkey = serialize_privkey(
       +            privkey, compressed, txin_type, internal_use=True)
       +        # NOTE: if the same pubkey is reused for multiple addresses (script types),
       +        # there will only be one pubkey-privkey pair for it in self.keypairs,
       +        # and the privkey will encode a txin_type but that txin_type cannot be trusted.
       +        # Removing keys complicates this further.
       +        self.keypairs[pubkey] = pw_encode(serialized_privkey, password)
       +        return txin_type, pubkey
       +
       +    def delete_imported_key(self, key):
       +        self.keypairs.pop(key)
       +
       +    def get_private_key(self, pubkey, password):
       +        sec = pw_decode(self.keypairs[pubkey], password)
       +        txin_type, privkey, compressed = deserialize_privkey(sec)
       +        # this checks the password
       +        if pubkey != ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed):
       +            raise InvalidPassword()
       +        return privkey, compressed
       +
       +    def get_pubkey_derivation(self, x_pubkey):
       +        if x_pubkey[0:2] in ['02', '03', '04']:
       +            if x_pubkey in self.keypairs.keys():
       +                return x_pubkey
       +        elif x_pubkey[0:2] == 'fd':
       +            addr = bitcoin.script_to_address(x_pubkey[2:])
       +            if addr in self.addresses:
       +                return self.addresses[addr].get('pubkey')
       +
       +    def update_password(self, old_password, new_password):
       +        self.check_password(old_password)
       +        if new_password == '':
       +            new_password = None
       +        for k, v in self.keypairs.items():
       +            b = pw_decode(v, old_password)
       +            c = pw_encode(b, new_password)
       +            self.keypairs[k] = c
       +
       +
       +
       +class Deterministic_KeyStore(Software_KeyStore):
       +
       +    def __init__(self, d):
       +        Software_KeyStore.__init__(self)
       +        self.seed = d.get('seed', '')
       +        self.passphrase = d.get('passphrase', '')
       +
       +    def is_deterministic(self):
       +        return True
       +
       +    def dump(self):
       +        d = {}
       +        if self.seed:
       +            d['seed'] = self.seed
       +        if self.passphrase:
       +            d['passphrase'] = self.passphrase
       +        return d
       +
       +    def has_seed(self):
       +        return bool(self.seed)
       +
       +    def is_watching_only(self):
       +        return not self.has_seed()
       +
       +    def add_seed(self, seed):
       +        if self.seed:
       +            raise Exception("a seed exists")
       +        self.seed = self.format_seed(seed)
       +
       +    def get_seed(self, password):
       +        return pw_decode(self.seed, password)
       +
       +    def get_passphrase(self, password):
       +        return pw_decode(self.passphrase, password) if self.passphrase else ''
       +
       +
       +class Xpub:
       +
       +    def __init__(self):
       +        self.xpub = None
       +        self.xpub_receive = None
       +        self.xpub_change = None
       +
       +    def get_master_public_key(self):
       +        return self.xpub
       +
       +    def derive_pubkey(self, for_change, n):
       +        xpub = self.xpub_change if for_change else self.xpub_receive
       +        if xpub is None:
       +            xpub = bip32_public_derivation(self.xpub, "", "/%d"%for_change)
       +            if for_change:
       +                self.xpub_change = xpub
       +            else:
       +                self.xpub_receive = xpub
       +        return self.get_pubkey_from_xpub(xpub, (n,))
       +
       +    @classmethod
       +    def get_pubkey_from_xpub(self, xpub, sequence):
       +        _, _, _, _, c, cK = deserialize_xpub(xpub)
       +        for i in sequence:
       +            cK, c = CKD_pub(cK, c, i)
       +        return bh2u(cK)
       +
       +    def get_xpubkey(self, c, i):
       +        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (c, i)))
       +        return 'ff' + bh2u(bitcoin.DecodeBase58Check(self.xpub)) + s
       +
       +    @classmethod
       +    def parse_xpubkey(self, pubkey):
       +        assert pubkey[0:2] == 'ff'
       +        pk = bfh(pubkey)
       +        pk = pk[1:]
       +        xkey = bitcoin.EncodeBase58Check(pk[0:78])
       +        dd = pk[78:]
       +        s = []
       +        while dd:
       +            n = int(bitcoin.rev_hex(bh2u(dd[0:2])), 16)
       +            dd = dd[2:]
       +            s.append(n)
       +        assert len(s) == 2
       +        return xkey, s
       +
       +    def get_pubkey_derivation(self, x_pubkey):
       +        if x_pubkey[0:2] != 'ff':
       +            return
       +        xpub, derivation = self.parse_xpubkey(x_pubkey)
       +        if self.xpub != xpub:
       +            return
       +        return derivation
       +
       +
       +class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
       +
       +    def __init__(self, d):
       +        Xpub.__init__(self)
       +        Deterministic_KeyStore.__init__(self, d)
       +        self.xpub = d.get('xpub')
       +        self.xprv = d.get('xprv')
       +
       +    def format_seed(self, seed):
       +        return ' '.join(seed.split())
       +
       +    def dump(self):
       +        d = Deterministic_KeyStore.dump(self)
       +        d['type'] = 'bip32'
       +        d['xpub'] = self.xpub
       +        d['xprv'] = self.xprv
       +        return d
       +
       +    def get_master_private_key(self, password):
       +        return pw_decode(self.xprv, password)
       +
       +    def check_password(self, password):
       +        xprv = pw_decode(self.xprv, password)
       +        if deserialize_xprv(xprv)[4] != deserialize_xpub(self.xpub)[4]:
       +            raise InvalidPassword()
       +
       +    def update_password(self, old_password, new_password):
       +        self.check_password(old_password)
       +        if new_password == '':
       +            new_password = None
       +        if self.has_seed():
       +            decoded = self.get_seed(old_password)
       +            self.seed = pw_encode(decoded, new_password)
       +        if self.passphrase:
       +            decoded = self.get_passphrase(old_password)
       +            self.passphrase = pw_encode(decoded, new_password)
       +        if self.xprv is not None:
       +            b = pw_decode(self.xprv, old_password)
       +            self.xprv = pw_encode(b, new_password)
       +
       +    def is_watching_only(self):
       +        return self.xprv is None
       +
       +    def add_xprv(self, xprv):
       +        self.xprv = xprv
       +        self.xpub = bitcoin.xpub_from_xprv(xprv)
       +
       +    def add_xprv_from_seed(self, bip32_seed, xtype, derivation):
       +        xprv, xpub = bip32_root(bip32_seed, xtype)
       +        xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
       +        self.add_xprv(xprv)
       +
       +    def get_private_key(self, sequence, password):
       +        xprv = self.get_master_private_key(password)
       +        _, _, _, _, c, k = deserialize_xprv(xprv)
       +        pk = bip32_private_key(sequence, k, c)
       +        return pk, True
       +
       +
       +
       +class Old_KeyStore(Deterministic_KeyStore):
       +
       +    def __init__(self, d):
       +        Deterministic_KeyStore.__init__(self, d)
       +        self.mpk = d.get('mpk')
       +
       +    def get_hex_seed(self, password):
       +        return pw_decode(self.seed, password).encode('utf8')
       +
       +    def dump(self):
       +        d = Deterministic_KeyStore.dump(self)
       +        d['mpk'] = self.mpk
       +        d['type'] = 'old'
       +        return d
       +
       +    def add_seed(self, seedphrase):
       +        Deterministic_KeyStore.add_seed(self, seedphrase)
       +        s = self.get_hex_seed(None)
       +        self.mpk = self.mpk_from_seed(s)
       +
       +    def add_master_public_key(self, mpk):
       +        self.mpk = mpk
       +
       +    def format_seed(self, seed):
       +        from . import old_mnemonic, mnemonic
       +        seed = mnemonic.normalize_text(seed)
       +        # see if seed was entered as hex
       +        if seed:
       +            try:
       +                bfh(seed)
       +                return str(seed)
       +            except Exception:
       +                pass
       +        words = seed.split()
       +        seed = old_mnemonic.mn_decode(words)
       +        if not seed:
       +            raise Exception("Invalid seed")
       +        return seed
       +
       +    def get_seed(self, password):
       +        from . import old_mnemonic
       +        s = self.get_hex_seed(password)
       +        return ' '.join(old_mnemonic.mn_encode(s))
       +
       +    @classmethod
       +    def mpk_from_seed(klass, seed):
       +        secexp = klass.stretch_key(seed)
       +        privkey = ecc.ECPrivkey.from_secret_scalar(secexp)
       +        return privkey.get_public_key_hex(compressed=False)[2:]
       +
       +    @classmethod
       +    def stretch_key(self, seed):
       +        x = seed
       +        for i in range(100000):
       +            x = hashlib.sha256(x + seed).digest()
       +        return string_to_number(x)
       +
       +    @classmethod
       +    def get_sequence(self, mpk, for_change, n):
       +        return string_to_number(Hash(("%d:%d:"%(n, for_change)).encode('ascii') + bfh(mpk)))
       +
       +    @classmethod
       +    def get_pubkey_from_mpk(self, mpk, for_change, n):
       +        z = self.get_sequence(mpk, for_change, n)
       +        master_public_key = ecc.ECPubkey(bfh('04'+mpk))
       +        public_key = master_public_key + z*ecc.generator()
       +        return public_key.get_public_key_hex(compressed=False)
       +
       +    def derive_pubkey(self, for_change, n):
       +        return self.get_pubkey_from_mpk(self.mpk, for_change, n)
       +
       +    def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
       +        secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % ecc.CURVE_ORDER
       +        pk = number_to_string(secexp, ecc.CURVE_ORDER)
       +        return pk
       +
       +    def get_private_key(self, sequence, password):
       +        seed = self.get_hex_seed(password)
       +        self.check_seed(seed)
       +        for_change, n = sequence
       +        secexp = self.stretch_key(seed)
       +        pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)
       +        return pk, False
       +
       +    def check_seed(self, seed):
       +        secexp = self.stretch_key(seed)
       +        master_private_key = ecc.ECPrivkey.from_secret_scalar(secexp)
       +        master_public_key = master_private_key.get_public_key_bytes(compressed=False)[1:]
       +        if master_public_key != bfh(self.mpk):
       +            print_error('invalid password (mpk)', self.mpk, bh2u(master_public_key))
       +            raise InvalidPassword()
       +
       +    def check_password(self, password):
       +        seed = self.get_hex_seed(password)
       +        self.check_seed(seed)
       +
       +    def get_master_public_key(self):
       +        return self.mpk
       +
       +    def get_xpubkey(self, for_change, n):
       +        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
       +        return 'fe' + self.mpk + s
       +
       +    @classmethod
       +    def parse_xpubkey(self, x_pubkey):
       +        assert x_pubkey[0:2] == 'fe'
       +        pk = x_pubkey[2:]
       +        mpk = pk[0:128]
       +        dd = pk[128:]
       +        s = []
       +        while dd:
       +            n = int(bitcoin.rev_hex(dd[0:4]), 16)
       +            dd = dd[4:]
       +            s.append(n)
       +        assert len(s) == 2
       +        return mpk, s
       +
       +    def get_pubkey_derivation(self, x_pubkey):
       +        if x_pubkey[0:2] != 'fe':
       +            return
       +        mpk, derivation = self.parse_xpubkey(x_pubkey)
       +        if self.mpk != mpk:
       +            return
       +        return derivation
       +
       +    def update_password(self, old_password, new_password):
       +        self.check_password(old_password)
       +        if new_password == '':
       +            new_password = None
       +        if self.has_seed():
       +            decoded = pw_decode(self.seed, old_password)
       +            self.seed = pw_encode(decoded, new_password)
       +
       +
       +
       +class Hardware_KeyStore(KeyStore, Xpub):
       +    # Derived classes must set:
       +    #   - device
       +    #   - DEVICE_IDS
       +    #   - wallet_type
       +
       +    #restore_wallet_class = BIP32_RD_Wallet
       +    max_change_outputs = 1
       +
       +    def __init__(self, d):
       +        Xpub.__init__(self)
       +        KeyStore.__init__(self)
       +        # Errors and other user interaction is done through the wallet's
       +        # handler.  The handler is per-window and preserved across
       +        # device reconnects
       +        self.xpub = d.get('xpub')
       +        self.label = d.get('label')
       +        self.derivation = d.get('derivation')
       +        self.handler = None
       +        run_hook('init_keystore', self)
       +
       +    def set_label(self, label):
       +        self.label = label
       +
       +    def may_have_password(self):
       +        return False
       +
       +    def is_deterministic(self):
       +        return True
       +
       +    def dump(self):
       +        return {
       +            'type': 'hardware',
       +            'hw_type': self.hw_type,
       +            'xpub': self.xpub,
       +            'derivation':self.derivation,
       +            'label':self.label,
       +        }
       +
       +    def unpaired(self):
       +        '''A device paired with the wallet was disconnected.  This can be
       +        called in any thread context.'''
       +        self.print_error("unpaired")
       +
       +    def paired(self):
       +        '''A device paired with the wallet was (re-)connected.  This can be
       +        called in any thread context.'''
       +        self.print_error("paired")
       +
       +    def can_export(self):
       +        return False
       +
       +    def is_watching_only(self):
       +        '''The wallet is not watching-only; the user will be prompted for
       +        pin and passphrase as appropriate when needed.'''
       +        assert not self.has_seed()
       +        return False
       +
       +    def get_password_for_storage_encryption(self):
       +        from .storage import get_derivation_used_for_hw_device_encryption
       +        client = self.plugin.get_client(self)
       +        derivation = get_derivation_used_for_hw_device_encryption()
       +        xpub = client.get_xpub(derivation, "standard")
       +        password = self.get_pubkey_from_xpub(xpub, ())
       +        return password
       +
       +    def has_usable_connection_with_device(self):
       +        if not hasattr(self, 'plugin'):
       +            return False
       +        client = self.plugin.get_client(self, force_pair=False)
       +        if client is None:
       +            return False
       +        return client.has_usable_connection_with_device()
       +
       +    def ready_to_sign(self):
       +        return super().ready_to_sign() and self.has_usable_connection_with_device()
       +
       +
       +def bip39_normalize_passphrase(passphrase):
       +    return normalize('NFKD', passphrase or '')
       +
       +def bip39_to_seed(mnemonic, passphrase):
       +    import pbkdf2, hashlib, hmac
       +    PBKDF2_ROUNDS = 2048
       +    mnemonic = normalize('NFKD', ' '.join(mnemonic.split()))
       +    passphrase = bip39_normalize_passphrase(passphrase)
       +    return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase,
       +                         iterations = PBKDF2_ROUNDS, macmodule = hmac,
       +                         digestmodule = hashlib.sha512).read(64)
       +
       +# returns tuple (is_checksum_valid, is_wordlist_valid)
       +def bip39_is_checksum_valid(mnemonic):
       +    words = [ normalize('NFKD', word) for word in mnemonic.split() ]
       +    words_len = len(words)
       +    wordlist = load_wordlist("english.txt")
       +    n = len(wordlist)
       +    checksum_length = 11*words_len//33
       +    entropy_length = 32*checksum_length
       +    i = 0
       +    words.reverse()
       +    while words:
       +        w = words.pop()
       +        try:
       +            k = wordlist.index(w)
       +        except ValueError:
       +            return False, False
       +        i = i*n + k
       +    if words_len not in [12, 15, 18, 21, 24]:
       +        return False, True
       +    entropy = i >> checksum_length
       +    checksum = i % 2**checksum_length
       +    h = '{:x}'.format(entropy)
       +    while len(h) < entropy_length/4:
       +        h = '0'+h
       +    b = bytearray.fromhex(h)
       +    hashed = int(hfu(hashlib.sha256(b).digest()), 16)
       +    calculated_checksum = hashed >> (256 - checksum_length)
       +    return checksum == calculated_checksum, True
       +
       +
       +def from_bip39_seed(seed, passphrase, derivation, xtype=None):
       +    k = BIP32_KeyStore({})
       +    bip32_seed = bip39_to_seed(seed, passphrase)
       +    if xtype is None:
       +        xtype = xtype_from_derivation(derivation)
       +    k.add_xprv_from_seed(bip32_seed, xtype, derivation)
       +    return k
       +
       +
       +def xtype_from_derivation(derivation: str) -> str:
       +    """Returns the script type to be used for this derivation."""
       +    if derivation.startswith("m/84'"):
       +        return 'p2wpkh'
       +    elif derivation.startswith("m/49'"):
       +        return 'p2wpkh-p2sh'
       +    elif derivation.startswith("m/44'"):
       +        return 'standard'
       +    elif derivation.startswith("m/45'"):
       +        return 'standard'
       +
       +    bip32_indices = list(bip32_derivation(derivation))
       +    if len(bip32_indices) >= 4:
       +        if bip32_indices[0] == 48 + BIP32_PRIME:
       +            # m / purpose' / coin_type' / account' / script_type' / change / address_index
       +            script_type_int = bip32_indices[3] - BIP32_PRIME
       +            script_type = PURPOSE48_SCRIPT_TYPES_INV.get(script_type_int)
       +            if script_type is not None:
       +                return script_type
       +    return 'standard'
       +
       +
       +# extended pubkeys
       +
       +def is_xpubkey(x_pubkey):
       +    return x_pubkey[0:2] == 'ff'
       +
       +
       +def parse_xpubkey(x_pubkey):
       +    assert x_pubkey[0:2] == 'ff'
       +    return BIP32_KeyStore.parse_xpubkey(x_pubkey)
       +
       +
       +def xpubkey_to_address(x_pubkey):
       +    if x_pubkey[0:2] == 'fd':
       +        address = bitcoin.script_to_address(x_pubkey[2:])
       +        return x_pubkey, address
       +    if x_pubkey[0:2] in ['02', '03', '04']:
       +        pubkey = x_pubkey
       +    elif x_pubkey[0:2] == 'ff':
       +        xpub, s = BIP32_KeyStore.parse_xpubkey(x_pubkey)
       +        pubkey = BIP32_KeyStore.get_pubkey_from_xpub(xpub, s)
       +    elif x_pubkey[0:2] == 'fe':
       +        mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)
       +        pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1])
       +    else:
       +        raise BitcoinException("Cannot parse pubkey. prefix: {}"
       +                               .format(x_pubkey[0:2]))
       +    if pubkey:
       +        address = public_key_to_p2pkh(bfh(pubkey))
       +    return pubkey, address
       +
       +def xpubkey_to_pubkey(x_pubkey):
       +    pubkey, address = xpubkey_to_address(x_pubkey)
       +    return pubkey
       +
       +hw_keystores = {}
       +
       +def register_keystore(hw_type, constructor):
       +    hw_keystores[hw_type] = constructor
       +
       +def hardware_keystore(d):
       +    hw_type = d['hw_type']
       +    if hw_type in hw_keystores:
       +        constructor = hw_keystores[hw_type]
       +        return constructor(d)
       +    raise WalletFileException('unknown hardware type: {}. hw_keystores: {}'.format(hw_type, list(hw_keystores.keys())))
       +
       +def load_keystore(storage, name):
       +    d = storage.get(name, {})
       +    t = d.get('type')
       +    if not t:
       +        raise WalletFileException(
       +            'Wallet format requires update.\n'
       +            'Cannot find keystore for name {}'.format(name))
       +    if t == 'old':
       +        k = Old_KeyStore(d)
       +    elif t == 'imported':
       +        k = Imported_KeyStore(d)
       +    elif t == 'bip32':
       +        k = BIP32_KeyStore(d)
       +    elif t == 'hardware':
       +        k = hardware_keystore(d)
       +    else:
       +        raise WalletFileException(
       +            'Unknown type {} for keystore named {}'.format(t, name))
       +    return k
       +
       +
       +def is_old_mpk(mpk: str) -> bool:
       +    try:
       +        int(mpk, 16)
       +    except:
       +        return False
       +    if len(mpk) != 128:
       +        return False
       +    try:
       +        ecc.ECPubkey(bfh('04' + mpk))
       +    except:
       +        return False
       +    return True
       +
       +
       +def is_address_list(text):
       +    parts = text.split()
       +    return bool(parts) and all(bitcoin.is_address(x) for x in parts)
       +
       +
       +def get_private_keys(text):
       +    parts = text.split('\n')
       +    parts = map(lambda x: ''.join(x.split()), parts)
       +    parts = list(filter(bool, parts))
       +    if bool(parts) and all(bitcoin.is_private_key(x) for x in parts):
       +        return parts
       +
       +
       +def is_private_key_list(text):
       +    return bool(get_private_keys(text))
       +
       +
       +is_mpk = lambda x: is_old_mpk(x) or is_xpub(x)
       +is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)
       +is_master_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x)
       +is_private_key = lambda x: is_xprv(x) or is_private_key_list(x)
       +is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
       +
       +
       +def bip44_derivation(account_id, bip43_purpose=44):
       +    coin = constants.net.BIP44_COIN_TYPE
       +    return "m/%d'/%d'/%d'" % (bip43_purpose, coin, int(account_id))
       +
       +
       +def purpose48_derivation(account_id: int, xtype: str) -> str:
       +    # m / purpose' / coin_type' / account' / script_type' / change / address_index
       +    bip43_purpose = 48
       +    coin = constants.net.BIP44_COIN_TYPE
       +    account_id = int(account_id)
       +    script_type_int = PURPOSE48_SCRIPT_TYPES.get(xtype)
       +    if script_type_int is None:
       +        raise Exception('unknown xtype: {}'.format(xtype))
       +    return "m/%d'/%d'/%d'/%d'" % (bip43_purpose, coin, account_id, script_type_int)
       +
       +
       +def from_seed(seed, passphrase, is_p2sh):
       +    t = seed_type(seed)
       +    if t == 'old':
       +        keystore = Old_KeyStore({})
       +        keystore.add_seed(seed)
       +    elif t in ['standard', 'segwit']:
       +        keystore = BIP32_KeyStore({})
       +        keystore.add_seed(seed)
       +        keystore.passphrase = passphrase
       +        bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
       +        if t == 'standard':
       +            der = "m/"
       +            xtype = 'standard'
       +        else:
       +            der = "m/1'/" if is_p2sh else "m/0'/"
       +            xtype = 'p2wsh' if is_p2sh else 'p2wpkh'
       +        keystore.add_xprv_from_seed(bip32_seed, xtype, der)
       +    else:
       +        raise BitcoinException('Unexpected seed type {}'.format(t))
       +    return keystore
       +
       +def from_private_key_list(text):
       +    keystore = Imported_KeyStore({})
       +    for x in get_private_keys(text):
       +        keystore.import_key(x, None)
       +    return keystore
       +
       +def from_old_mpk(mpk):
       +    keystore = Old_KeyStore({})
       +    keystore.add_master_public_key(mpk)
       +    return keystore
       +
       +def from_xpub(xpub):
       +    k = BIP32_KeyStore({})
       +    k.xpub = xpub
       +    return k
       +
       +def from_xprv(xprv):
       +    xpub = bitcoin.xpub_from_xprv(xprv)
       +    k = BIP32_KeyStore({})
       +    k.xprv = xprv
       +    k.xpub = xpub
       +    return k
       +
       +def from_master_key(text):
       +    if is_xprv(text):
       +        k = from_xprv(text)
       +    elif is_old_mpk(text):
       +        k = from_old_mpk(text)
       +    elif is_xpub(text):
       +        k = from_xpub(text)
       +    else:
       +        raise BitcoinException('Invalid master key')
       +    return k
   DIR diff --git a/electrum/mnemonic.py b/electrum/mnemonic.py
       t@@ -0,0 +1,183 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2014 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import os
       +import hmac
       +import math
       +import hashlib
       +import unicodedata
       +import string
       +
       +import ecdsa
       +import pbkdf2
       +
       +from .util import print_error
       +from .bitcoin import is_old_seed, is_new_seed
       +from . import version
       +
       +# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html
       +CJK_INTERVALS = [
       +    (0x4E00, 0x9FFF, 'CJK Unified Ideographs'),
       +    (0x3400, 0x4DBF, 'CJK Unified Ideographs Extension A'),
       +    (0x20000, 0x2A6DF, 'CJK Unified Ideographs Extension B'),
       +    (0x2A700, 0x2B73F, 'CJK Unified Ideographs Extension C'),
       +    (0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'),
       +    (0xF900, 0xFAFF, 'CJK Compatibility Ideographs'),
       +    (0x2F800, 0x2FA1D, 'CJK Compatibility Ideographs Supplement'),
       +    (0x3190, 0x319F , 'Kanbun'),
       +    (0x2E80, 0x2EFF, 'CJK Radicals Supplement'),
       +    (0x2F00, 0x2FDF, 'CJK Radicals'),
       +    (0x31C0, 0x31EF, 'CJK Strokes'),
       +    (0x2FF0, 0x2FFF, 'Ideographic Description Characters'),
       +    (0xE0100, 0xE01EF, 'Variation Selectors Supplement'),
       +    (0x3100, 0x312F, 'Bopomofo'),
       +    (0x31A0, 0x31BF, 'Bopomofo Extended'),
       +    (0xFF00, 0xFFEF, 'Halfwidth and Fullwidth Forms'),
       +    (0x3040, 0x309F, 'Hiragana'),
       +    (0x30A0, 0x30FF, 'Katakana'),
       +    (0x31F0, 0x31FF, 'Katakana Phonetic Extensions'),
       +    (0x1B000, 0x1B0FF, 'Kana Supplement'),
       +    (0xAC00, 0xD7AF, 'Hangul Syllables'),
       +    (0x1100, 0x11FF, 'Hangul Jamo'),
       +    (0xA960, 0xA97F, 'Hangul Jamo Extended A'),
       +    (0xD7B0, 0xD7FF, 'Hangul Jamo Extended B'),
       +    (0x3130, 0x318F, 'Hangul Compatibility Jamo'),
       +    (0xA4D0, 0xA4FF, 'Lisu'),
       +    (0x16F00, 0x16F9F, 'Miao'),
       +    (0xA000, 0xA48F, 'Yi Syllables'),
       +    (0xA490, 0xA4CF, 'Yi Radicals'),
       +]
       +
       +def is_CJK(c):
       +    n = ord(c)
       +    for imin,imax,name in CJK_INTERVALS:
       +        if n>=imin and n<=imax: return True
       +    return False
       +
       +
       +def normalize_text(seed):
       +    # normalize
       +    seed = unicodedata.normalize('NFKD', seed)
       +    # lower
       +    seed = seed.lower()
       +    # remove accents
       +    seed = u''.join([c for c in seed if not unicodedata.combining(c)])
       +    # normalize whitespaces
       +    seed = u' '.join(seed.split())
       +    # remove whitespaces between CJK
       +    seed = u''.join([seed[i] for i in range(len(seed)) if not (seed[i] in string.whitespace and is_CJK(seed[i-1]) and is_CJK(seed[i+1]))])
       +    return seed
       +
       +def load_wordlist(filename):
       +    path = os.path.join(os.path.dirname(__file__), 'wordlist', filename)
       +    with open(path, 'r', encoding='utf-8') as f:
       +        s = f.read().strip()
       +    s = unicodedata.normalize('NFKD', s)
       +    lines = s.split('\n')
       +    wordlist = []
       +    for line in lines:
       +        line = line.split('#')[0]
       +        line = line.strip(' \r')
       +        assert ' ' not in line
       +        if line:
       +            wordlist.append(line)
       +    return wordlist
       +
       +
       +filenames = {
       +    'en':'english.txt',
       +    'es':'spanish.txt',
       +    'ja':'japanese.txt',
       +    'pt':'portuguese.txt',
       +    'zh':'chinese_simplified.txt'
       +}
       +
       +
       +
       +class Mnemonic(object):
       +    # Seed derivation no longer follows BIP39
       +    # Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum
       +
       +    def __init__(self, lang=None):
       +        lang = lang or 'en'
       +        print_error('language', lang)
       +        filename = filenames.get(lang[0:2], 'english.txt')
       +        self.wordlist = load_wordlist(filename)
       +        print_error("wordlist has %d words"%len(self.wordlist))
       +
       +    @classmethod
       +    def mnemonic_to_seed(self, mnemonic, passphrase):
       +        PBKDF2_ROUNDS = 2048
       +        mnemonic = normalize_text(mnemonic)
       +        passphrase = normalize_text(passphrase)
       +        return pbkdf2.PBKDF2(mnemonic, 'electrum' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
       +
       +    def mnemonic_encode(self, i):
       +        n = len(self.wordlist)
       +        words = []
       +        while i:
       +            x = i%n
       +            i = i//n
       +            words.append(self.wordlist[x])
       +        return ' '.join(words)
       +
       +    def get_suggestions(self, prefix):
       +        for w in self.wordlist:
       +            if w.startswith(prefix):
       +                yield w
       +
       +    def mnemonic_decode(self, seed):
       +        n = len(self.wordlist)
       +        words = seed.split()
       +        i = 0
       +        while words:
       +            w = words.pop()
       +            k = self.wordlist.index(w)
       +            i = i*n + k
       +        return i
       +
       +    def make_seed(self, seed_type='standard', num_bits=132):
       +        prefix = version.seed_prefix(seed_type)
       +        # increase num_bits in order to obtain a uniform distribution for the last word
       +        bpw = math.log(len(self.wordlist), 2)
       +        # rounding
       +        n = int(math.ceil(num_bits/bpw) * bpw)
       +        print_error("make_seed. prefix: '%s'"%prefix, "entropy: %d bits"%n)
       +        entropy = 1
       +        while entropy < pow(2, n - bpw):
       +            # try again if seed would not contain enough words
       +            entropy = ecdsa.util.randrange(pow(2, n))
       +        nonce = 0
       +        while True:
       +            nonce += 1
       +            i = entropy + nonce
       +            seed = self.mnemonic_encode(i)
       +            if i != self.mnemonic_decode(seed):
       +                raise Exception('Cannot extract same entropy from mnemonic!')
       +            if is_old_seed(seed):
       +                continue
       +            if is_new_seed(seed, prefix):
       +                break
       +        print_error('%d words'%len(seed.split()))
       +        return seed
   DIR diff --git a/electrum/msqr.py b/electrum/msqr.py
       t@@ -0,0 +1,94 @@
       +# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
       +
       +def modular_sqrt(a, p):
       +    """ Find a quadratic residue (mod p) of 'a'. p
       +    must be an odd prime.
       +
       +    Solve the congruence of the form:
       +    x^2 = a (mod p)
       +    And returns x. Note that p - x is also a root.
       +
       +    0 is returned is no square root exists for
       +    these a and p.
       +
       +    The Tonelli-Shanks algorithm is used (except
       +    for some simple cases in which the solution
       +    is known from an identity). This algorithm
       +    runs in polynomial time (unless the
       +    generalized Riemann hypothesis is false).
       +    """
       +    # Simple cases
       +    #
       +    if legendre_symbol(a, p) != 1:
       +        return 0
       +    elif a == 0:
       +        return 0
       +    elif p == 2:
       +        return p
       +    elif p % 4 == 3:
       +        return pow(a, (p + 1) // 4, p)
       +
       +    # Partition p-1 to s * 2^e for an odd s (i.e.
       +    # reduce all the powers of 2 from p-1)
       +    #
       +    s = p - 1
       +    e = 0
       +    while s % 2 == 0:
       +        s //= 2
       +        e += 1
       +
       +    # Find some 'n' with a legendre symbol n|p = -1.
       +    # Shouldn't take long.
       +    #
       +    n = 2
       +    while legendre_symbol(n, p) != -1:
       +        n += 1
       +
       +    # Here be dragons!
       +    # Read the paper "Square roots from 1; 24, 51,
       +    # 10 to Dan Shanks" by Ezra Brown for more
       +    # information
       +    #
       +
       +    # x is a guess of the square root that gets better
       +    # with each iteration.
       +    # b is the "fudge factor" - by how much we're off
       +    # with the guess. The invariant x^2 = ab (mod p)
       +    # is maintained throughout the loop.
       +    # g is used for successive powers of n to update
       +    # both a and b
       +    # r is the exponent - decreases with each update
       +    #
       +    x = pow(a, (s + 1) // 2, p)
       +    b = pow(a, s, p)
       +    g = pow(n, s, p)
       +    r = e
       +
       +    while True:
       +        t = b
       +        m = 0
       +        for m in range(r):
       +            if t == 1:
       +                break
       +            t = pow(t, 2, p)
       +
       +        if m == 0:
       +            return x
       +
       +        gs = pow(g, 2 ** (r - m - 1), p)
       +        g = (gs * gs) % p
       +        x = (x * gs) % p
       +        b = (b * g) % p
       +        r = m
       +
       +def legendre_symbol(a, p):
       +    """ Compute the Legendre symbol a|p using
       +    Euler's criterion. p is a prime, a is
       +    relatively prime to p (if p divides
       +    a, then a|p = 0)
       +
       +    Returns 1 if a has a square root modulo
       +    p, -1 otherwise.
       +    """
       +    ls = pow(a, (p - 1) // 2, p)
       +    return -1 if ls == p - 1 else ls
   DIR diff --git a/electrum/network.py b/electrum/network.py
       t@@ -0,0 +1,1297 @@
       +# Electrum - Lightweight Bitcoin Client
       +# Copyright (c) 2011-2016 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import time
       +import queue
       +import os
       +import errno
       +import random
       +import re
       +import select
       +from collections import defaultdict
       +import threading
       +import socket
       +import json
       +import sys
       +import ipaddress
       +
       +import dns
       +import dns.resolver
       +import socks
       +
       +from . import util
       +from .util import print_error
       +from . import bitcoin
       +from .bitcoin import COIN
       +from . import constants
       +from .interface import Connection, Interface
       +from . import blockchain
       +from .version import ELECTRUM_VERSION, PROTOCOL_VERSION
       +from .i18n import _
       +
       +
       +NODES_RETRY_INTERVAL = 60
       +SERVER_RETRY_INTERVAL = 10
       +
       +
       +def parse_servers(result):
       +    """ parse servers list into dict format"""
       +    servers = {}
       +    for item in result:
       +        host = item[1]
       +        out = {}
       +        version = None
       +        pruning_level = '-'
       +        if len(item) > 2:
       +            for v in item[2]:
       +                if re.match("[st]\d*", v):
       +                    protocol, port = v[0], v[1:]
       +                    if port == '': port = constants.net.DEFAULT_PORTS[protocol]
       +                    out[protocol] = port
       +                elif re.match("v(.?)+", v):
       +                    version = v[1:]
       +                elif re.match("p\d*", v):
       +                    pruning_level = v[1:]
       +                if pruning_level == '': pruning_level = '0'
       +        if out:
       +            out['pruning'] = pruning_level
       +            out['version'] = version
       +            servers[host] = out
       +    return servers
       +
       +
       +def filter_version(servers):
       +    def is_recent(version):
       +        try:
       +            return util.normalize_version(version) >= util.normalize_version(PROTOCOL_VERSION)
       +        except Exception as e:
       +            return False
       +    return {k: v for k, v in servers.items() if is_recent(v.get('version'))}
       +
       +
       +def filter_protocol(hostmap, protocol='s'):
       +    '''Filters the hostmap for those implementing protocol.
       +    The result is a list in serialized form.'''
       +    eligible = []
       +    for host, portmap in hostmap.items():
       +        port = portmap.get(protocol)
       +        if port:
       +            eligible.append(serialize_server(host, port, protocol))
       +    return eligible
       +
       +
       +def pick_random_server(hostmap = None, protocol = 's', exclude_set = set()):
       +    if hostmap is None:
       +        hostmap = constants.net.DEFAULT_SERVERS
       +    eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set)
       +    return random.choice(eligible) if eligible else None
       +
       +
       +from .simple_config import SimpleConfig
       +
       +proxy_modes = ['socks4', 'socks5', 'http']
       +
       +
       +def serialize_proxy(p):
       +    if not isinstance(p, dict):
       +        return None
       +    return ':'.join([p.get('mode'), p.get('host'), p.get('port'),
       +                     p.get('user', ''), p.get('password', '')])
       +
       +
       +def deserialize_proxy(s):
       +    if not isinstance(s, str):
       +        return None
       +    if s.lower() == 'none':
       +        return None
       +    proxy = { "mode":"socks5", "host":"localhost" }
       +    args = s.split(':')
       +    n = 0
       +    if proxy_modes.count(args[n]) == 1:
       +        proxy["mode"] = args[n]
       +        n += 1
       +    if len(args) > n:
       +        proxy["host"] = args[n]
       +        n += 1
       +    if len(args) > n:
       +        proxy["port"] = args[n]
       +        n += 1
       +    else:
       +        proxy["port"] = "8080" if proxy["mode"] == "http" else "1080"
       +    if len(args) > n:
       +        proxy["user"] = args[n]
       +        n += 1
       +    if len(args) > n:
       +        proxy["password"] = args[n]
       +    return proxy
       +
       +
       +def deserialize_server(server_str):
       +    host, port, protocol = str(server_str).rsplit(':', 2)
       +    if protocol not in 'st':
       +        raise ValueError('invalid network protocol: {}'.format(protocol))
       +    int(port)    # Throw if cannot be converted to int
       +    return host, port, protocol
       +
       +
       +def serialize_server(host, port, protocol):
       +    return str(':'.join([host, port, protocol]))
       +
       +
       +class Network(util.DaemonThread):
       +    """The Network class manages a set of connections to remote electrum
       +    servers, each connected socket is handled by an Interface() object.
       +    Connections are initiated by a Connection() thread which stops once
       +    the connection succeeds or fails.
       +
       +    Our external API:
       +
       +    - Member functions get_header(), get_interfaces(), get_local_height(),
       +          get_parameters(), get_server_height(), get_status_value(),
       +          is_connected(), set_parameters(), stop()
       +    """
       +
       +    def __init__(self, config=None):
       +        if config is None:
       +            config = {}  # Do not use mutables as default values!
       +        util.DaemonThread.__init__(self)
       +        self.config = SimpleConfig(config) if isinstance(config, dict) else config
       +        self.num_server = 10 if not self.config.get('oneserver') else 0
       +        self.blockchains = blockchain.read_blockchains(self.config)  # note: needs self.blockchains_lock
       +        self.print_error("blockchains", self.blockchains.keys())
       +        self.blockchain_index = config.get('blockchain_index', 0)
       +        if self.blockchain_index not in self.blockchains.keys():
       +            self.blockchain_index = 0
       +        # Server for addresses and transactions
       +        self.default_server = self.config.get('server', None)
       +        # Sanitize default server
       +        if self.default_server:
       +            try:
       +                deserialize_server(self.default_server)
       +            except:
       +                self.print_error('Warning: failed to parse server-string; falling back to random.')
       +                self.default_server = None
       +        if not self.default_server:
       +            self.default_server = pick_random_server()
       +
       +        # locks: if you need to take multiple ones, acquire them in the order they are defined here!
       +        self.interface_lock = threading.RLock()            # <- re-entrant
       +        self.callback_lock = threading.Lock()
       +        self.pending_sends_lock = threading.Lock()
       +        self.recent_servers_lock = threading.RLock()       # <- re-entrant
       +        self.subscribed_addresses_lock = threading.Lock()
       +        self.blockchains_lock = threading.Lock()
       +
       +        self.pending_sends = []
       +        self.message_id = 0
       +        self.debug = False
       +        self.irc_servers = {}  # returned by interface (list from irc)
       +        self.recent_servers = self.read_recent_servers()  # note: needs self.recent_servers_lock
       +
       +        self.banner = ''
       +        self.donation_address = ''
       +        self.relay_fee = None
       +        # callbacks passed with subscriptions
       +        self.subscriptions = defaultdict(list)  # note: needs self.callback_lock
       +        self.sub_cache = {}                     # note: needs self.interface_lock
       +        # callbacks set by the GUI
       +        self.callbacks = defaultdict(list)      # note: needs self.callback_lock
       +
       +        dir_path = os.path.join(self.config.path, 'certs')
       +        util.make_dir(dir_path)
       +
       +        # subscriptions and requests
       +        self.subscribed_addresses = set()  # note: needs self.subscribed_addresses_lock
       +        self.h2addr = {}
       +        # Requests from client we've not seen a response to
       +        self.unanswered_requests = {}
       +        # retry times
       +        self.server_retry_time = time.time()
       +        self.nodes_retry_time = time.time()
       +        # kick off the network.  interface is the main server we are currently
       +        # communicating with.  interfaces is the set of servers we are connecting
       +        # to or have an ongoing connection with
       +        self.interface = None              # note: needs self.interface_lock
       +        self.interfaces = {}               # note: needs self.interface_lock
       +        self.auto_connect = self.config.get('auto_connect', True)
       +        self.connecting = set()
       +        self.requested_chunks = set()
       +        self.socket_queue = queue.Queue()
       +        self.start_network(deserialize_server(self.default_server)[2],
       +                           deserialize_proxy(self.config.get('proxy')))
       +
       +    def with_interface_lock(func):
       +        def func_wrapper(self, *args, **kwargs):
       +            with self.interface_lock:
       +                return func(self, *args, **kwargs)
       +        return func_wrapper
       +
       +    def with_recent_servers_lock(func):
       +        def func_wrapper(self, *args, **kwargs):
       +            with self.recent_servers_lock:
       +                return func(self, *args, **kwargs)
       +        return func_wrapper
       +
       +    def register_callback(self, callback, events):
       +        with self.callback_lock:
       +            for event in events:
       +                self.callbacks[event].append(callback)
       +
       +    def unregister_callback(self, callback):
       +        with self.callback_lock:
       +            for callbacks in self.callbacks.values():
       +                if callback in callbacks:
       +                    callbacks.remove(callback)
       +
       +    def trigger_callback(self, event, *args):
       +        with self.callback_lock:
       +            callbacks = self.callbacks[event][:]
       +        [callback(event, *args) for callback in callbacks]
       +
       +    def read_recent_servers(self):
       +        if not self.config.path:
       +            return []
       +        path = os.path.join(self.config.path, "recent_servers")
       +        try:
       +            with open(path, "r", encoding='utf-8') as f:
       +                data = f.read()
       +                return json.loads(data)
       +        except:
       +            return []
       +
       +    @with_recent_servers_lock
       +    def save_recent_servers(self):
       +        if not self.config.path:
       +            return
       +        path = os.path.join(self.config.path, "recent_servers")
       +        s = json.dumps(self.recent_servers, indent=4, sort_keys=True)
       +        try:
       +            with open(path, "w", encoding='utf-8') as f:
       +                f.write(s)
       +        except:
       +            pass
       +
       +    @with_interface_lock
       +    def get_server_height(self):
       +        return self.interface.tip if self.interface else 0
       +
       +    def server_is_lagging(self):
       +        sh = self.get_server_height()
       +        if not sh:
       +            self.print_error('no height for main interface')
       +            return True
       +        lh = self.get_local_height()
       +        result = (lh - sh) > 1
       +        if result:
       +            self.print_error('%s is lagging (%d vs %d)' % (self.default_server, sh, lh))
       +        return result
       +
       +    def set_status(self, status):
       +        self.connection_status = status
       +        self.notify('status')
       +
       +    def is_connected(self):
       +        return self.interface is not None
       +
       +    def is_connecting(self):
       +        return self.connection_status == 'connecting'
       +
       +    @with_interface_lock
       +    def queue_request(self, method, params, interface=None):
       +        # If you want to queue a request on any interface it must go
       +        # through this function so message ids are properly tracked
       +        if interface is None:
       +            interface = self.interface
       +        if interface is None:
       +            self.print_error('warning: dropping request', method, params)
       +            return
       +        message_id = self.message_id
       +        self.message_id += 1
       +        if self.debug:
       +            self.print_error(interface.host, "-->", method, params, message_id)
       +        interface.queue_request(method, params, message_id)
       +        return message_id
       +
       +    @with_interface_lock
       +    def send_subscriptions(self):
       +        assert self.interface
       +        self.print_error('sending subscriptions to', self.interface.server, len(self.unanswered_requests), len(self.subscribed_addresses))
       +        self.sub_cache.clear()
       +        # Resend unanswered requests
       +        requests = self.unanswered_requests.values()
       +        self.unanswered_requests = {}
       +        for request in requests:
       +            message_id = self.queue_request(request[0], request[1])
       +            self.unanswered_requests[message_id] = request
       +        self.queue_request('server.banner', [])
       +        self.queue_request('server.donation_address', [])
       +        self.queue_request('server.peers.subscribe', [])
       +        self.request_fee_estimates()
       +        self.queue_request('blockchain.relayfee', [])
       +        with self.subscribed_addresses_lock:
       +            for h in self.subscribed_addresses:
       +                self.queue_request('blockchain.scripthash.subscribe', [h])
       +
       +    def request_fee_estimates(self):
       +        from .simple_config import FEE_ETA_TARGETS
       +        self.config.requested_fee_estimates()
       +        self.queue_request('mempool.get_fee_histogram', [])
       +        for i in FEE_ETA_TARGETS:
       +            self.queue_request('blockchain.estimatefee', [i])
       +
       +    def get_status_value(self, key):
       +        if key == 'status':
       +            value = self.connection_status
       +        elif key == 'banner':
       +            value = self.banner
       +        elif key == 'fee':
       +            value = self.config.fee_estimates
       +        elif key == 'fee_histogram':
       +            value = self.config.mempool_fees
       +        elif key == 'updated':
       +            value = (self.get_local_height(), self.get_server_height())
       +        elif key == 'servers':
       +            value = self.get_servers()
       +        elif key == 'interfaces':
       +            value = self.get_interfaces()
       +        return value
       +
       +    def notify(self, key):
       +        if key in ['status', 'updated']:
       +            self.trigger_callback(key)
       +        else:
       +            self.trigger_callback(key, self.get_status_value(key))
       +
       +    def get_parameters(self):
       +        host, port, protocol = deserialize_server(self.default_server)
       +        return host, port, protocol, self.proxy, self.auto_connect
       +
       +    def get_donation_address(self):
       +        if self.is_connected():
       +            return self.donation_address
       +
       +    @with_interface_lock
       +    def get_interfaces(self):
       +        '''The interfaces that are in connected state'''
       +        return list(self.interfaces.keys())
       +
       +    @with_recent_servers_lock
       +    def get_servers(self):
       +        out = constants.net.DEFAULT_SERVERS
       +        if self.irc_servers:
       +            out.update(filter_version(self.irc_servers.copy()))
       +        else:
       +            for s in self.recent_servers:
       +                try:
       +                    host, port, protocol = deserialize_server(s)
       +                except:
       +                    continue
       +                if host not in out:
       +                    out[host] = {protocol: port}
       +        return out
       +
       +    @with_interface_lock
       +    def start_interface(self, server):
       +        if (not server in self.interfaces and not server in self.connecting):
       +            if server == self.default_server:
       +                self.print_error("connecting to %s as new interface" % server)
       +                self.set_status('connecting')
       +            self.connecting.add(server)
       +            Connection(server, self.socket_queue, self.config.path)
       +
       +    def start_random_interface(self):
       +        with self.interface_lock:
       +            exclude_set = self.disconnected_servers.union(set(self.interfaces))
       +        server = pick_random_server(self.get_servers(), self.protocol, exclude_set)
       +        if server:
       +            self.start_interface(server)
       +
       +    def start_interfaces(self):
       +        self.start_interface(self.default_server)
       +        for i in range(self.num_server - 1):
       +            self.start_random_interface()
       +
       +    def set_proxy(self, proxy):
       +        self.proxy = proxy
       +        # Store these somewhere so we can un-monkey-patch
       +        if not hasattr(socket, "_socketobject"):
       +            socket._socketobject = socket.socket
       +            socket._getaddrinfo = socket.getaddrinfo
       +        if proxy:
       +            self.print_error('setting proxy', proxy)
       +            proxy_mode = proxy_modes.index(proxy["mode"]) + 1
       +            socks.setdefaultproxy(proxy_mode,
       +                                  proxy["host"],
       +                                  int(proxy["port"]),
       +                                  # socks.py seems to want either None or a non-empty string
       +                                  username=(proxy.get("user", "") or None),
       +                                  password=(proxy.get("password", "") or None))
       +            socket.socket = socks.socksocket
       +            # prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy
       +            socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]
       +        else:
       +            socket.socket = socket._socketobject
       +            if sys.platform == 'win32':
       +                # On Windows, socket.getaddrinfo takes a mutex, and might hold it for up to 10 seconds
       +                # when dns-resolving. To speed it up drastically, we resolve dns ourselves, outside that lock.
       +                # see #4421
       +                socket.getaddrinfo = self._fast_getaddrinfo
       +            else:
       +                socket.getaddrinfo = socket._getaddrinfo
       +
       +    @staticmethod
       +    def _fast_getaddrinfo(host, *args, **kwargs):
       +        def needs_dns_resolving(host2):
       +            try:
       +                ipaddress.ip_address(host2)
       +                return False  # already valid IP
       +            except ValueError:
       +                pass  # not an IP
       +            if str(host) in ('localhost', 'localhost.',):
       +                return False
       +            return True
       +        try:
       +            if needs_dns_resolving(host):
       +                answers = dns.resolver.query(host)
       +                addr = str(answers[0])
       +            else:
       +                addr = host
       +        except dns.exception.DNSException:
       +            # dns failed for some reason, e.g. dns.resolver.NXDOMAIN
       +            # this is normal. Simply report back failure:
       +            raise socket.gaierror(11001, 'getaddrinfo failed')
       +        except BaseException as e:
       +            # Possibly internal error in dnspython :( see #4483
       +            # Fall back to original socket.getaddrinfo to resolve dns.
       +            print_error('dnspython failed to resolve dns with error:', e)
       +            addr = host
       +        return socket._getaddrinfo(addr, *args, **kwargs)
       +
       +    @with_interface_lock
       +    def start_network(self, protocol, proxy):
       +        assert not self.interface and not self.interfaces
       +        assert not self.connecting and self.socket_queue.empty()
       +        self.print_error('starting network')
       +        self.disconnected_servers = set([])  # note: needs self.interface_lock
       +        self.protocol = protocol
       +        self.set_proxy(proxy)
       +        self.start_interfaces()
       +
       +    @with_interface_lock
       +    def stop_network(self):
       +        self.print_error("stopping network")
       +        for interface in list(self.interfaces.values()):
       +            self.close_interface(interface)
       +        if self.interface:
       +            self.close_interface(self.interface)
       +        assert self.interface is None
       +        assert not self.interfaces
       +        self.connecting = set()
       +        # Get a new queue - no old pending connections thanks!
       +        self.socket_queue = queue.Queue()
       +
       +    def set_parameters(self, host, port, protocol, proxy, auto_connect):
       +        proxy_str = serialize_proxy(proxy)
       +        server = serialize_server(host, port, protocol)
       +        # sanitize parameters
       +        try:
       +            deserialize_server(serialize_server(host, port, protocol))
       +            if proxy:
       +                proxy_modes.index(proxy["mode"]) + 1
       +                int(proxy['port'])
       +        except:
       +            return
       +        self.config.set_key('auto_connect', auto_connect, False)
       +        self.config.set_key("proxy", proxy_str, False)
       +        self.config.set_key("server", server, True)
       +        # abort if changes were not allowed by config
       +        if self.config.get('server') != server or self.config.get('proxy') != proxy_str:
       +            return
       +        self.auto_connect = auto_connect
       +        if self.proxy != proxy or self.protocol != protocol:
       +            # Restart the network defaulting to the given server
       +            with self.interface_lock:
       +                self.stop_network()
       +                self.default_server = server
       +                self.start_network(protocol, proxy)
       +        elif self.default_server != server:
       +            self.switch_to_interface(server)
       +        else:
       +            self.switch_lagging_interface()
       +            self.notify('updated')
       +
       +    def switch_to_random_interface(self):
       +        '''Switch to a random connected server other than the current one'''
       +        servers = self.get_interfaces()    # Those in connected state
       +        if self.default_server in servers:
       +            servers.remove(self.default_server)
       +        if servers:
       +            self.switch_to_interface(random.choice(servers))
       +
       +    @with_interface_lock
       +    def switch_lagging_interface(self):
       +        '''If auto_connect and lagging, switch interface'''
       +        if self.server_is_lagging() and self.auto_connect:
       +            # switch to one that has the correct header (not height)
       +            header = self.blockchain().read_header(self.get_local_height())
       +            filtered = list(map(lambda x: x[0], filter(lambda x: x[1].tip_header == header, self.interfaces.items())))
       +            if filtered:
       +                choice = random.choice(filtered)
       +                self.switch_to_interface(choice)
       +
       +    @with_interface_lock
       +    def switch_to_interface(self, server):
       +        '''Switch to server as our interface.  If no connection exists nor
       +        being opened, start a thread to connect.  The actual switch will
       +        happen on receipt of the connection notification.  Do nothing
       +        if server already is our interface.'''
       +        self.default_server = server
       +        if server not in self.interfaces:
       +            self.interface = None
       +            self.start_interface(server)
       +            return
       +
       +        i = self.interfaces[server]
       +        if self.interface != i:
       +            self.print_error("switching to", server)
       +            # stop any current interface in order to terminate subscriptions
       +            # fixme: we don't want to close headers sub
       +            #self.close_interface(self.interface)
       +            self.interface = i
       +            self.send_subscriptions()
       +            self.set_status('connected')
       +            self.notify('updated')
       +            self.notify('interfaces')
       +
       +    @with_interface_lock
       +    def close_interface(self, interface):
       +        if interface:
       +            if interface.server in self.interfaces:
       +                self.interfaces.pop(interface.server)
       +            if interface.server == self.default_server:
       +                self.interface = None
       +            interface.close()
       +
       +    @with_recent_servers_lock
       +    def add_recent_server(self, server):
       +        # list is ordered
       +        if server in self.recent_servers:
       +            self.recent_servers.remove(server)
       +        self.recent_servers.insert(0, server)
       +        self.recent_servers = self.recent_servers[0:20]
       +        self.save_recent_servers()
       +
       +    def process_response(self, interface, response, callbacks):
       +        if self.debug:
       +            self.print_error(interface.host, "<--", response)
       +        error = response.get('error')
       +        result = response.get('result')
       +        method = response.get('method')
       +        params = response.get('params')
       +
       +        # We handle some responses; return the rest to the client.
       +        if method == 'server.version':
       +            interface.server_version = result
       +        elif method == 'blockchain.headers.subscribe':
       +            if error is None:
       +                self.on_notify_header(interface, result)
       +            else:
       +                # no point in keeping this connection without headers sub
       +                self.connection_down(interface.server)
       +                return
       +        elif method == 'server.peers.subscribe':
       +            if error is None:
       +                self.irc_servers = parse_servers(result)
       +                self.notify('servers')
       +        elif method == 'server.banner':
       +            if error is None:
       +                self.banner = result
       +                self.notify('banner')
       +        elif method == 'server.donation_address':
       +            if error is None:
       +                self.donation_address = result
       +        elif method == 'mempool.get_fee_histogram':
       +            if error is None:
       +                self.print_error('fee_histogram', result)
       +                self.config.mempool_fees = result
       +                self.notify('fee_histogram')
       +        elif method == 'blockchain.estimatefee':
       +            if error is None and result > 0:
       +                i = params[0]
       +                fee = int(result*COIN)
       +                self.config.update_fee_estimates(i, fee)
       +                self.print_error("fee_estimates[%d]" % i, fee)
       +                self.notify('fee')
       +        elif method == 'blockchain.relayfee':
       +            if error is None:
       +                self.relay_fee = int(result * COIN) if result is not None else None
       +                self.print_error("relayfee", self.relay_fee)
       +        elif method == 'blockchain.block.headers':
       +            self.on_block_headers(interface, response)
       +        elif method == 'blockchain.block.get_header':
       +            self.on_get_header(interface, response)
       +
       +        for callback in callbacks:
       +            callback(response)
       +
       +    @classmethod
       +    def get_index(cls, method, params):
       +        """ hashable index for subscriptions and cache"""
       +        return str(method) + (':' + str(params[0]) if params else '')
       +
       +    def process_responses(self, interface):
       +        responses = interface.get_responses()
       +        for request, response in responses:
       +            if request:
       +                method, params, message_id = request
       +                k = self.get_index(method, params)
       +                # client requests go through self.send() with a
       +                # callback, are only sent to the current interface,
       +                # and are placed in the unanswered_requests dictionary
       +                client_req = self.unanswered_requests.pop(message_id, None)
       +                if client_req:
       +                    if interface != self.interface:
       +                        # we probably changed the current interface
       +                        # in the meantime; drop this.
       +                        return
       +                    callbacks = [client_req[2]]
       +                else:
       +                    # fixme: will only work for subscriptions
       +                    k = self.get_index(method, params)
       +                    callbacks = list(self.subscriptions.get(k, []))
       +
       +                # Copy the request method and params to the response
       +                response['method'] = method
       +                response['params'] = params
       +                # Only once we've received a response to an addr subscription
       +                # add it to the list; avoids double-sends on reconnection
       +                if method == 'blockchain.scripthash.subscribe':
       +                    with self.subscribed_addresses_lock:
       +                        self.subscribed_addresses.add(params[0])
       +            else:
       +                if not response:  # Closed remotely / misbehaving
       +                    self.connection_down(interface.server)
       +                    break
       +                # Rewrite response shape to match subscription request response
       +                method = response.get('method')
       +                params = response.get('params')
       +                k = self.get_index(method, params)
       +                if method == 'blockchain.headers.subscribe':
       +                    response['result'] = params[0]
       +                    response['params'] = []
       +                elif method == 'blockchain.scripthash.subscribe':
       +                    response['params'] = [params[0]]  # addr
       +                    response['result'] = params[1]
       +                callbacks = list(self.subscriptions.get(k, []))
       +
       +            # update cache if it's a subscription
       +            if method.endswith('.subscribe'):
       +                with self.interface_lock:
       +                    self.sub_cache[k] = response
       +            # Response is now in canonical form
       +            self.process_response(interface, response, callbacks)
       +
       +    def send(self, messages, callback):
       +        '''Messages is a list of (method, params) tuples'''
       +        messages = list(messages)
       +        with self.pending_sends_lock:
       +            self.pending_sends.append((messages, callback))
       +
       +    @with_interface_lock
       +    def process_pending_sends(self):
       +        # Requests needs connectivity.  If we don't have an interface,
       +        # we cannot process them.
       +        if not self.interface:
       +            return
       +
       +        with self.pending_sends_lock:
       +            sends = self.pending_sends
       +            self.pending_sends = []
       +
       +        for messages, callback in sends:
       +            for method, params in messages:
       +                r = None
       +                if method.endswith('.subscribe'):
       +                    k = self.get_index(method, params)
       +                    # add callback to list
       +                    l = list(self.subscriptions.get(k, []))
       +                    if callback not in l:
       +                        l.append(callback)
       +                    with self.callback_lock:
       +                        self.subscriptions[k] = l
       +                    # check cached response for subscriptions
       +                    r = self.sub_cache.get(k)
       +
       +                if r is not None:
       +                    self.print_error("cache hit", k)
       +                    callback(r)
       +                else:
       +                    message_id = self.queue_request(method, params)
       +                    self.unanswered_requests[message_id] = method, params, callback
       +
       +    def unsubscribe(self, callback):
       +        '''Unsubscribe a callback to free object references to enable GC.'''
       +        # Note: we can't unsubscribe from the server, so if we receive
       +        # subsequent notifications process_response() will emit a harmless
       +        # "received unexpected notification" warning
       +        with self.callback_lock:
       +            for v in self.subscriptions.values():
       +                if callback in v:
       +                    v.remove(callback)
       +
       +    @with_interface_lock
       +    def connection_down(self, server):
       +        '''A connection to server either went down, or was never made.
       +        We distinguish by whether it is in self.interfaces.'''
       +        self.disconnected_servers.add(server)
       +        if server == self.default_server:
       +            self.set_status('disconnected')
       +        if server in self.interfaces:
       +            self.close_interface(self.interfaces[server])
       +            self.notify('interfaces')
       +        with self.blockchains_lock:
       +            for b in self.blockchains.values():
       +                if b.catch_up == server:
       +                    b.catch_up = None
       +
       +    def new_interface(self, server, socket):
       +        # todo: get tip first, then decide which checkpoint to use.
       +        self.add_recent_server(server)
       +        interface = Interface(server, socket)
       +        interface.blockchain = None
       +        interface.tip_header = None
       +        interface.tip = 0
       +        interface.mode = 'default'
       +        interface.request = None
       +        with self.interface_lock:
       +            self.interfaces[server] = interface
       +        # server.version should be the first message
       +        params = [ELECTRUM_VERSION, PROTOCOL_VERSION]
       +        self.queue_request('server.version', params, interface)
       +        self.queue_request('blockchain.headers.subscribe', [True], interface)
       +        if server == self.default_server:
       +            self.switch_to_interface(server)
       +        #self.notify('interfaces')
       +
       +    def maintain_sockets(self):
       +        '''Socket maintenance.'''
       +        # Responses to connection attempts?
       +        while not self.socket_queue.empty():
       +            server, socket = self.socket_queue.get()
       +            if server in self.connecting:
       +                self.connecting.remove(server)
       +
       +            if socket:
       +                self.new_interface(server, socket)
       +            else:
       +                self.connection_down(server)
       +
       +        # Send pings and shut down stale interfaces
       +        # must use copy of values
       +        with self.interface_lock:
       +            interfaces = list(self.interfaces.values())
       +        for interface in interfaces:
       +            if interface.has_timed_out():
       +                self.connection_down(interface.server)
       +            elif interface.ping_required():
       +                self.queue_request('server.ping', [], interface)
       +
       +        now = time.time()
       +        # nodes
       +        with self.interface_lock:
       +            if len(self.interfaces) + len(self.connecting) < self.num_server:
       +                self.start_random_interface()
       +                if now - self.nodes_retry_time > NODES_RETRY_INTERVAL:
       +                    self.print_error('network: retrying connections')
       +                    self.disconnected_servers = set([])
       +                    self.nodes_retry_time = now
       +
       +        # main interface
       +        with self.interface_lock:
       +            if not self.is_connected():
       +                if self.auto_connect:
       +                    if not self.is_connecting():
       +                        self.switch_to_random_interface()
       +                else:
       +                    if self.default_server in self.disconnected_servers:
       +                        if now - self.server_retry_time > SERVER_RETRY_INTERVAL:
       +                            self.disconnected_servers.remove(self.default_server)
       +                            self.server_retry_time = now
       +                    else:
       +                        self.switch_to_interface(self.default_server)
       +            else:
       +                if self.config.is_fee_estimates_update_required():
       +                    self.request_fee_estimates()
       +
       +    def request_chunk(self, interface, index):
       +        if index in self.requested_chunks:
       +            return
       +        interface.print_error("requesting chunk %d" % index)
       +        self.requested_chunks.add(index)
       +        height = index * 2016
       +        self.queue_request('blockchain.block.headers', [height, 2016],
       +                           interface)
       +
       +    def on_block_headers(self, interface, response):
       +        '''Handle receiving a chunk of block headers'''
       +        error = response.get('error')
       +        result = response.get('result')
       +        params = response.get('params')
       +        blockchain = interface.blockchain
       +        if result is None or params is None or error is not None:
       +            interface.print_error(error or 'bad response')
       +            return
       +        # Ignore unsolicited chunks
       +        height = params[0]
       +        index = height // 2016
       +        if index * 2016 != height or index not in self.requested_chunks:
       +            interface.print_error("received chunk %d (unsolicited)" % index)
       +            return
       +        else:
       +            interface.print_error("received chunk %d" % index)
       +        self.requested_chunks.remove(index)
       +        hexdata = result['hex']
       +        connect = blockchain.connect_chunk(index, hexdata)
       +        if not connect:
       +            self.connection_down(interface.server)
       +            return
       +        # If not finished, get the next chunk
       +        if index >= len(blockchain.checkpoints) and blockchain.height() < interface.tip:
       +            self.request_chunk(interface, index+1)
       +        else:
       +            interface.mode = 'default'
       +            interface.print_error('catch up done', blockchain.height())
       +            blockchain.catch_up = None
       +        self.notify('updated')
       +
       +    def on_get_header(self, interface, response):
       +        '''Handle receiving a single block header'''
       +        header = response.get('result')
       +        if not header:
       +            interface.print_error(response)
       +            self.connection_down(interface.server)
       +            return
       +        height = header.get('block_height')
       +        if interface.request != height:
       +            interface.print_error("unsolicited header",interface.request, height)
       +            self.connection_down(interface.server)
       +            return
       +        chain = blockchain.check_header(header)
       +        if interface.mode == 'backward':
       +            can_connect = blockchain.can_connect(header)
       +            if can_connect and can_connect.catch_up is None:
       +                interface.mode = 'catch_up'
       +                interface.blockchain = can_connect
       +                interface.blockchain.save_header(header)
       +                next_height = height + 1
       +                interface.blockchain.catch_up = interface.server
       +            elif chain:
       +                interface.print_error("binary search")
       +                interface.mode = 'binary'
       +                interface.blockchain = chain
       +                interface.good = height
       +                next_height = (interface.bad + interface.good) // 2
       +                assert next_height >= self.max_checkpoint(), (interface.bad, interface.good)
       +            else:
       +                if height == 0:
       +                    self.connection_down(interface.server)
       +                    next_height = None
       +                else:
       +                    interface.bad = height
       +                    interface.bad_header = header
       +                    delta = interface.tip - height
       +                    next_height = max(self.max_checkpoint(), interface.tip - 2 * delta)
       +                    if height == next_height:
       +                        self.connection_down(interface.server)
       +                        next_height = None
       +
       +        elif interface.mode == 'binary':
       +            if chain:
       +                interface.good = height
       +                interface.blockchain = chain
       +            else:
       +                interface.bad = height
       +                interface.bad_header = header
       +            if interface.bad != interface.good + 1:
       +                next_height = (interface.bad + interface.good) // 2
       +                assert next_height >= self.max_checkpoint()
       +            elif not interface.blockchain.can_connect(interface.bad_header, check_height=False):
       +                self.connection_down(interface.server)
       +                next_height = None
       +            else:
       +                branch = self.blockchains.get(interface.bad)
       +                if branch is not None:
       +                    if branch.check_header(interface.bad_header):
       +                        interface.print_error('joining chain', interface.bad)
       +                        next_height = None
       +                    elif branch.parent().check_header(header):
       +                        interface.print_error('reorg', interface.bad, interface.tip)
       +                        interface.blockchain = branch.parent()
       +                        next_height = None
       +                    else:
       +                        interface.print_error('checkpoint conflicts with existing fork', branch.path())
       +                        branch.write(b'', 0)
       +                        branch.save_header(interface.bad_header)
       +                        interface.mode = 'catch_up'
       +                        interface.blockchain = branch
       +                        next_height = interface.bad + 1
       +                        interface.blockchain.catch_up = interface.server
       +                else:
       +                    bh = interface.blockchain.height()
       +                    next_height = None
       +                    if bh > interface.good:
       +                        if not interface.blockchain.check_header(interface.bad_header):
       +                            b = interface.blockchain.fork(interface.bad_header)
       +                            with self.blockchains_lock:
       +                                self.blockchains[interface.bad] = b
       +                            interface.blockchain = b
       +                            interface.print_error("new chain", b.checkpoint)
       +                            interface.mode = 'catch_up'
       +                            next_height = interface.bad + 1
       +                            interface.blockchain.catch_up = interface.server
       +                    else:
       +                        assert bh == interface.good
       +                        if interface.blockchain.catch_up is None and bh < interface.tip:
       +                            interface.print_error("catching up from %d"% (bh + 1))
       +                            interface.mode = 'catch_up'
       +                            next_height = bh + 1
       +                            interface.blockchain.catch_up = interface.server
       +
       +                self.notify('updated')
       +
       +        elif interface.mode == 'catch_up':
       +            can_connect = interface.blockchain.can_connect(header)
       +            if can_connect:
       +                interface.blockchain.save_header(header)
       +                next_height = height + 1 if height < interface.tip else None
       +            else:
       +                # go back
       +                interface.print_error("cannot connect", height)
       +                interface.mode = 'backward'
       +                interface.bad = height
       +                interface.bad_header = header
       +                next_height = height - 1
       +
       +            if next_height is None:
       +                # exit catch_up state
       +                interface.print_error('catch up done', interface.blockchain.height())
       +                interface.blockchain.catch_up = None
       +                self.switch_lagging_interface()
       +                self.notify('updated')
       +
       +        else:
       +            raise Exception(interface.mode)
       +        # If not finished, get the next header
       +        if next_height is not None:
       +            if interface.mode == 'catch_up' and interface.tip > next_height + 50:
       +                self.request_chunk(interface, next_height // 2016)
       +            else:
       +                self.request_header(interface, next_height)
       +        else:
       +            interface.mode = 'default'
       +            interface.request = None
       +            self.notify('updated')
       +
       +        # refresh network dialog
       +        self.notify('interfaces')
       +
       +    def maintain_requests(self):
       +        with self.interface_lock:
       +            interfaces = list(self.interfaces.values())
       +        for interface in interfaces:
       +            if interface.request and time.time() - interface.request_time > 20:
       +                interface.print_error("blockchain request timed out")
       +                self.connection_down(interface.server)
       +                continue
       +
       +    def wait_on_sockets(self):
       +        # Python docs say Windows doesn't like empty selects.
       +        # Sleep to prevent busy looping
       +        if not self.interfaces:
       +            time.sleep(0.1)
       +            return
       +        with self.interface_lock:
       +            interfaces = list(self.interfaces.values())
       +        rin = [i for i in interfaces]
       +        win = [i for i in interfaces if i.num_requests()]
       +        try:
       +            rout, wout, xout = select.select(rin, win, [], 0.1)
       +        except socket.error as e:
       +            if e.errno == errno.EINTR:
       +                return
       +            raise
       +        assert not xout
       +        for interface in wout:
       +            interface.send_requests()
       +        for interface in rout:
       +            self.process_responses(interface)
       +
       +    def init_headers_file(self):
       +        b = self.blockchains[0]
       +        filename = b.path()
       +        length = 80 * len(constants.net.CHECKPOINTS) * 2016
       +        if not os.path.exists(filename) or os.path.getsize(filename) < length:
       +            with open(filename, 'wb') as f:
       +                if length>0:
       +                    f.seek(length-1)
       +                    f.write(b'\x00')
       +        with b.lock:
       +            b.update_size()
       +
       +    def run(self):
       +        self.init_headers_file()
       +        while self.is_running():
       +            self.maintain_sockets()
       +            self.wait_on_sockets()
       +            self.maintain_requests()
       +            self.run_jobs()    # Synchronizer and Verifier
       +            self.process_pending_sends()
       +        self.stop_network()
       +        self.on_stop()
       +
       +    def on_notify_header(self, interface, header_dict):
       +        try:
       +            header_hex, height = header_dict['hex'], header_dict['height']
       +        except KeyError:
       +            # no point in keeping this connection without headers sub
       +            self.connection_down(interface.server)
       +            return
       +        header = blockchain.deserialize_header(util.bfh(header_hex), height)
       +        if height < self.max_checkpoint():
       +            self.connection_down(interface.server)
       +            return
       +        interface.tip_header = header
       +        interface.tip = height
       +        if interface.mode != 'default':
       +            return
       +        b = blockchain.check_header(header)
       +        if b:
       +            interface.blockchain = b
       +            self.switch_lagging_interface()
       +            self.notify('updated')
       +            self.notify('interfaces')
       +            return
       +        b = blockchain.can_connect(header)
       +        if b:
       +            interface.blockchain = b
       +            b.save_header(header)
       +            self.switch_lagging_interface()
       +            self.notify('updated')
       +            self.notify('interfaces')
       +            return
       +        with self.blockchains_lock:
       +            tip = max([x.height() for x in self.blockchains.values()])
       +        if tip >=0:
       +            interface.mode = 'backward'
       +            interface.bad = height
       +            interface.bad_header = header
       +            self.request_header(interface, min(tip +1, height - 1))
       +        else:
       +            chain = self.blockchains[0]
       +            if chain.catch_up is None:
       +                chain.catch_up = interface
       +                interface.mode = 'catch_up'
       +                interface.blockchain = chain
       +                with self.blockchains_lock:
       +                    self.print_error("switching to catchup mode", tip,  self.blockchains)
       +                self.request_header(interface, 0)
       +            else:
       +                self.print_error("chain already catching up with", chain.catch_up.server)
       +
       +    @with_interface_lock
       +    def blockchain(self):
       +        if self.interface and self.interface.blockchain is not None:
       +            self.blockchain_index = self.interface.blockchain.checkpoint
       +        return self.blockchains[self.blockchain_index]
       +
       +    @with_interface_lock
       +    def get_blockchains(self):
       +        out = {}
       +        with self.blockchains_lock:
       +            blockchain_items = list(self.blockchains.items())
       +        for k, b in blockchain_items:
       +            r = list(filter(lambda i: i.blockchain==b, list(self.interfaces.values())))
       +            if r:
       +                out[k] = r
       +        return out
       +
       +    def follow_chain(self, index):
       +        blockchain = self.blockchains.get(index)
       +        if blockchain:
       +            self.blockchain_index = index
       +            self.config.set_key('blockchain_index', index)
       +            with self.interface_lock:
       +                interfaces = list(self.interfaces.values())
       +            for i in interfaces:
       +                if i.blockchain == blockchain:
       +                    self.switch_to_interface(i.server)
       +                    break
       +        else:
       +            raise Exception('blockchain not found', index)
       +
       +        with self.interface_lock:
       +            if self.interface:
       +                server = self.interface.server
       +                host, port, protocol, proxy, auto_connect = self.get_parameters()
       +                host, port, protocol = server.split(':')
       +                self.set_parameters(host, port, protocol, proxy, auto_connect)
       +
       +    def get_local_height(self):
       +        return self.blockchain().height()
       +
       +    @staticmethod
       +    def __wait_for(it):
       +        """Wait for the result of calling lambda `it`."""
       +        q = queue.Queue()
       +        it(q.put)
       +        try:
       +            result = q.get(block=True, timeout=30)
       +        except queue.Empty:
       +            raise util.TimeoutException(_('Server did not answer'))
       +
       +        if result.get('error'):
       +            raise Exception(result.get('error'))
       +
       +        return result.get('result')
       +
       +    @staticmethod
       +    def __with_default_synchronous_callback(invocation, callback):
       +        """ Use this method if you want to make the network request
       +        synchronous. """
       +        if not callback:
       +            return Network.__wait_for(invocation)
       +
       +        invocation(callback)
       +
       +    def request_header(self, interface, height):
       +        self.queue_request('blockchain.block.get_header', [height], interface)
       +        interface.request = height
       +        interface.req_time = time.time()
       +
       +    def map_scripthash_to_address(self, callback):
       +        def cb2(x):
       +            x2 = x.copy()
       +            p = x2.pop('params')
       +            addr = self.h2addr[p[0]]
       +            x2['params'] = [addr]
       +            callback(x2)
       +        return cb2
       +
       +    def subscribe_to_addresses(self, addresses, callback):
       +        hash2address = {
       +            bitcoin.address_to_scripthash(address): address
       +            for address in addresses}
       +        self.h2addr.update(hash2address)
       +        msgs = [
       +            ('blockchain.scripthash.subscribe', [x])
       +            for x in hash2address.keys()]
       +        self.send(msgs, self.map_scripthash_to_address(callback))
       +
       +    def request_address_history(self, address, callback):
       +        h = bitcoin.address_to_scripthash(address)
       +        self.h2addr.update({h: address})
       +        self.send([('blockchain.scripthash.get_history', [h])], self.map_scripthash_to_address(callback))
       +
       +    # NOTE this method handles exceptions and a special edge case, counter to
       +    # what the other ElectrumX methods do. This is unexpected.
       +    def broadcast_transaction(self, transaction, callback=None):
       +        command = 'blockchain.transaction.broadcast'
       +        invocation = lambda c: self.send([(command, [str(transaction)])], c)
       +
       +        if callback:
       +            invocation(callback)
       +            return
       +
       +        try:
       +            out = Network.__wait_for(invocation)
       +        except BaseException as e:
       +            return False, "error: " + str(e)
       +
       +        if out != transaction.txid():
       +            return False, "error: " + out
       +
       +        return True, out
       +
       +    def get_history_for_scripthash(self, hash, callback=None):
       +        command = 'blockchain.scripthash.get_history'
       +        invocation = lambda c: self.send([(command, [hash])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def subscribe_to_headers(self, callback=None):
       +        command = 'blockchain.headers.subscribe'
       +        invocation = lambda c: self.send([(command, [True])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def subscribe_to_address(self, address, callback=None):
       +        command = 'blockchain.address.subscribe'
       +        invocation = lambda c: self.send([(command, [address])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def get_merkle_for_transaction(self, tx_hash, tx_height, callback=None):
       +        command = 'blockchain.transaction.get_merkle'
       +        invocation = lambda c: self.send([(command, [tx_hash, tx_height])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def subscribe_to_scripthash(self, scripthash, callback=None):
       +        command = 'blockchain.scripthash.subscribe'
       +        invocation = lambda c: self.send([(command, [scripthash])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def get_transaction(self, transaction_hash, callback=None):
       +        command = 'blockchain.transaction.get'
       +        invocation = lambda c: self.send([(command, [transaction_hash])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def get_transactions(self, transaction_hashes, callback=None):
       +        command = 'blockchain.transaction.get'
       +        messages = [(command, [tx_hash]) for tx_hash in transaction_hashes]
       +        invocation = lambda c: self.send(messages, c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def listunspent_for_scripthash(self, scripthash, callback=None):
       +        command = 'blockchain.scripthash.listunspent'
       +        invocation = lambda c: self.send([(command, [scripthash])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def get_balance_for_scripthash(self, scripthash, callback=None):
       +        command = 'blockchain.scripthash.get_balance'
       +        invocation = lambda c: self.send([(command, [scripthash])], c)
       +
       +        return Network.__with_default_synchronous_callback(invocation, callback)
       +
       +    def export_checkpoints(self, path):
       +        # run manually from the console to generate checkpoints
       +        cp = self.blockchain().get_checkpoints()
       +        with open(path, 'w', encoding='utf-8') as f:
       +            f.write(json.dumps(cp, indent=4))
       +
       +    @classmethod
       +    def max_checkpoint(cls):
       +        return max(0, len(constants.net.CHECKPOINTS) * 2016 - 1)
   DIR diff --git a/electrum/old_mnemonic.py b/electrum/old_mnemonic.py
       t@@ -0,0 +1,1697 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2011 thomasv@gitorious
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +
       +# list of words from http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry
       +
       +words = [
       +"like",
       +"just",
       +"love",
       +"know",
       +"never",
       +"want",
       +"time",
       +"out",
       +"there",
       +"make",
       +"look",
       +"eye",
       +"down",
       +"only",
       +"think",
       +"heart",
       +"back",
       +"then",
       +"into",
       +"about",
       +"more",
       +"away",
       +"still",
       +"them",
       +"take",
       +"thing",
       +"even",
       +"through",
       +"long",
       +"always",
       +"world",
       +"too",
       +"friend",
       +"tell",
       +"try",
       +"hand",
       +"thought",
       +"over",
       +"here",
       +"other",
       +"need",
       +"smile",
       +"again",
       +"much",
       +"cry",
       +"been",
       +"night",
       +"ever",
       +"little",
       +"said",
       +"end",
       +"some",
       +"those",
       +"around",
       +"mind",
       +"people",
       +"girl",
       +"leave",
       +"dream",
       +"left",
       +"turn",
       +"myself",
       +"give",
       +"nothing",
       +"really",
       +"off",
       +"before",
       +"something",
       +"find",
       +"walk",
       +"wish",
       +"good",
       +"once",
       +"place",
       +"ask",
       +"stop",
       +"keep",
       +"watch",
       +"seem",
       +"everything",
       +"wait",
       +"got",
       +"yet",
       +"made",
       +"remember",
       +"start",
       +"alone",
       +"run",
       +"hope",
       +"maybe",
       +"believe",
       +"body",
       +"hate",
       +"after",
       +"close",
       +"talk",
       +"stand",
       +"own",
       +"each",
       +"hurt",
       +"help",
       +"home",
       +"god",
       +"soul",
       +"new",
       +"many",
       +"two",
       +"inside",
       +"should",
       +"true",
       +"first",
       +"fear",
       +"mean",
       +"better",
       +"play",
       +"another",
       +"gone",
       +"change",
       +"use",
       +"wonder",
       +"someone",
       +"hair",
       +"cold",
       +"open",
       +"best",
       +"any",
       +"behind",
       +"happen",
       +"water",
       +"dark",
       +"laugh",
       +"stay",
       +"forever",
       +"name",
       +"work",
       +"show",
       +"sky",
       +"break",
       +"came",
       +"deep",
       +"door",
       +"put",
       +"black",
       +"together",
       +"upon",
       +"happy",
       +"such",
       +"great",
       +"white",
       +"matter",
       +"fill",
       +"past",
       +"please",
       +"burn",
       +"cause",
       +"enough",
       +"touch",
       +"moment",
       +"soon",
       +"voice",
       +"scream",
       +"anything",
       +"stare",
       +"sound",
       +"red",
       +"everyone",
       +"hide",
       +"kiss",
       +"truth",
       +"death",
       +"beautiful",
       +"mine",
       +"blood",
       +"broken",
       +"very",
       +"pass",
       +"next",
       +"forget",
       +"tree",
       +"wrong",
       +"air",
       +"mother",
       +"understand",
       +"lip",
       +"hit",
       +"wall",
       +"memory",
       +"sleep",
       +"free",
       +"high",
       +"realize",
       +"school",
       +"might",
       +"skin",
       +"sweet",
       +"perfect",
       +"blue",
       +"kill",
       +"breath",
       +"dance",
       +"against",
       +"fly",
       +"between",
       +"grow",
       +"strong",
       +"under",
       +"listen",
       +"bring",
       +"sometimes",
       +"speak",
       +"pull",
       +"person",
       +"become",
       +"family",
       +"begin",
       +"ground",
       +"real",
       +"small",
       +"father",
       +"sure",
       +"feet",
       +"rest",
       +"young",
       +"finally",
       +"land",
       +"across",
       +"today",
       +"different",
       +"guy",
       +"line",
       +"fire",
       +"reason",
       +"reach",
       +"second",
       +"slowly",
       +"write",
       +"eat",
       +"smell",
       +"mouth",
       +"step",
       +"learn",
       +"three",
       +"floor",
       +"promise",
       +"breathe",
       +"darkness",
       +"push",
       +"earth",
       +"guess",
       +"save",
       +"song",
       +"above",
       +"along",
       +"both",
       +"color",
       +"house",
       +"almost",
       +"sorry",
       +"anymore",
       +"brother",
       +"okay",
       +"dear",
       +"game",
       +"fade",
       +"already",
       +"apart",
       +"warm",
       +"beauty",
       +"heard",
       +"notice",
       +"question",
       +"shine",
       +"began",
       +"piece",
       +"whole",
       +"shadow",
       +"secret",
       +"street",
       +"within",
       +"finger",
       +"point",
       +"morning",
       +"whisper",
       +"child",
       +"moon",
       +"green",
       +"story",
       +"glass",
       +"kid",
       +"silence",
       +"since",
       +"soft",
       +"yourself",
       +"empty",
       +"shall",
       +"angel",
       +"answer",
       +"baby",
       +"bright",
       +"dad",
       +"path",
       +"worry",
       +"hour",
       +"drop",
       +"follow",
       +"power",
       +"war",
       +"half",
       +"flow",
       +"heaven",
       +"act",
       +"chance",
       +"fact",
       +"least",
       +"tired",
       +"children",
       +"near",
       +"quite",
       +"afraid",
       +"rise",
       +"sea",
       +"taste",
       +"window",
       +"cover",
       +"nice",
       +"trust",
       +"lot",
       +"sad",
       +"cool",
       +"force",
       +"peace",
       +"return",
       +"blind",
       +"easy",
       +"ready",
       +"roll",
       +"rose",
       +"drive",
       +"held",
       +"music",
       +"beneath",
       +"hang",
       +"mom",
       +"paint",
       +"emotion",
       +"quiet",
       +"clear",
       +"cloud",
       +"few",
       +"pretty",
       +"bird",
       +"outside",
       +"paper",
       +"picture",
       +"front",
       +"rock",
       +"simple",
       +"anyone",
       +"meant",
       +"reality",
       +"road",
       +"sense",
       +"waste",
       +"bit",
       +"leaf",
       +"thank",
       +"happiness",
       +"meet",
       +"men",
       +"smoke",
       +"truly",
       +"decide",
       +"self",
       +"age",
       +"book",
       +"form",
       +"alive",
       +"carry",
       +"escape",
       +"damn",
       +"instead",
       +"able",
       +"ice",
       +"minute",
       +"throw",
       +"catch",
       +"leg",
       +"ring",
       +"course",
       +"goodbye",
       +"lead",
       +"poem",
       +"sick",
       +"corner",
       +"desire",
       +"known",
       +"problem",
       +"remind",
       +"shoulder",
       +"suppose",
       +"toward",
       +"wave",
       +"drink",
       +"jump",
       +"woman",
       +"pretend",
       +"sister",
       +"week",
       +"human",
       +"joy",
       +"crack",
       +"grey",
       +"pray",
       +"surprise",
       +"dry",
       +"knee",
       +"less",
       +"search",
       +"bleed",
       +"caught",
       +"clean",
       +"embrace",
       +"future",
       +"king",
       +"son",
       +"sorrow",
       +"chest",
       +"hug",
       +"remain",
       +"sat",
       +"worth",
       +"blow",
       +"daddy",
       +"final",
       +"parent",
       +"tight",
       +"also",
       +"create",
       +"lonely",
       +"safe",
       +"cross",
       +"dress",
       +"evil",
       +"silent",
       +"bone",
       +"fate",
       +"perhaps",
       +"anger",
       +"class",
       +"scar",
       +"snow",
       +"tiny",
       +"tonight",
       +"continue",
       +"control",
       +"dog",
       +"edge",
       +"mirror",
       +"month",
       +"suddenly",
       +"comfort",
       +"given",
       +"loud",
       +"quickly",
       +"gaze",
       +"plan",
       +"rush",
       +"stone",
       +"town",
       +"battle",
       +"ignore",
       +"spirit",
       +"stood",
       +"stupid",
       +"yours",
       +"brown",
       +"build",
       +"dust",
       +"hey",
       +"kept",
       +"pay",
       +"phone",
       +"twist",
       +"although",
       +"ball",
       +"beyond",
       +"hidden",
       +"nose",
       +"taken",
       +"fail",
       +"float",
       +"pure",
       +"somehow",
       +"wash",
       +"wrap",
       +"angry",
       +"cheek",
       +"creature",
       +"forgotten",
       +"heat",
       +"rip",
       +"single",
       +"space",
       +"special",
       +"weak",
       +"whatever",
       +"yell",
       +"anyway",
       +"blame",
       +"job",
       +"choose",
       +"country",
       +"curse",
       +"drift",
       +"echo",
       +"figure",
       +"grew",
       +"laughter",
       +"neck",
       +"suffer",
       +"worse",
       +"yeah",
       +"disappear",
       +"foot",
       +"forward",
       +"knife",
       +"mess",
       +"somewhere",
       +"stomach",
       +"storm",
       +"beg",
       +"idea",
       +"lift",
       +"offer",
       +"breeze",
       +"field",
       +"five",
       +"often",
       +"simply",
       +"stuck",
       +"win",
       +"allow",
       +"confuse",
       +"enjoy",
       +"except",
       +"flower",
       +"seek",
       +"strength",
       +"calm",
       +"grin",
       +"gun",
       +"heavy",
       +"hill",
       +"large",
       +"ocean",
       +"shoe",
       +"sigh",
       +"straight",
       +"summer",
       +"tongue",
       +"accept",
       +"crazy",
       +"everyday",
       +"exist",
       +"grass",
       +"mistake",
       +"sent",
       +"shut",
       +"surround",
       +"table",
       +"ache",
       +"brain",
       +"destroy",
       +"heal",
       +"nature",
       +"shout",
       +"sign",
       +"stain",
       +"choice",
       +"doubt",
       +"glance",
       +"glow",
       +"mountain",
       +"queen",
       +"stranger",
       +"throat",
       +"tomorrow",
       +"city",
       +"either",
       +"fish",
       +"flame",
       +"rather",
       +"shape",
       +"spin",
       +"spread",
       +"ash",
       +"distance",
       +"finish",
       +"image",
       +"imagine",
       +"important",
       +"nobody",
       +"shatter",
       +"warmth",
       +"became",
       +"feed",
       +"flesh",
       +"funny",
       +"lust",
       +"shirt",
       +"trouble",
       +"yellow",
       +"attention",
       +"bare",
       +"bite",
       +"money",
       +"protect",
       +"amaze",
       +"appear",
       +"born",
       +"choke",
       +"completely",
       +"daughter",
       +"fresh",
       +"friendship",
       +"gentle",
       +"probably",
       +"six",
       +"deserve",
       +"expect",
       +"grab",
       +"middle",
       +"nightmare",
       +"river",
       +"thousand",
       +"weight",
       +"worst",
       +"wound",
       +"barely",
       +"bottle",
       +"cream",
       +"regret",
       +"relationship",
       +"stick",
       +"test",
       +"crush",
       +"endless",
       +"fault",
       +"itself",
       +"rule",
       +"spill",
       +"art",
       +"circle",
       +"join",
       +"kick",
       +"mask",
       +"master",
       +"passion",
       +"quick",
       +"raise",
       +"smooth",
       +"unless",
       +"wander",
       +"actually",
       +"broke",
       +"chair",
       +"deal",
       +"favorite",
       +"gift",
       +"note",
       +"number",
       +"sweat",
       +"box",
       +"chill",
       +"clothes",
       +"lady",
       +"mark",
       +"park",
       +"poor",
       +"sadness",
       +"tie",
       +"animal",
       +"belong",
       +"brush",
       +"consume",
       +"dawn",
       +"forest",
       +"innocent",
       +"pen",
       +"pride",
       +"stream",
       +"thick",
       +"clay",
       +"complete",
       +"count",
       +"draw",
       +"faith",
       +"press",
       +"silver",
       +"struggle",
       +"surface",
       +"taught",
       +"teach",
       +"wet",
       +"bless",
       +"chase",
       +"climb",
       +"enter",
       +"letter",
       +"melt",
       +"metal",
       +"movie",
       +"stretch",
       +"swing",
       +"vision",
       +"wife",
       +"beside",
       +"crash",
       +"forgot",
       +"guide",
       +"haunt",
       +"joke",
       +"knock",
       +"plant",
       +"pour",
       +"prove",
       +"reveal",
       +"steal",
       +"stuff",
       +"trip",
       +"wood",
       +"wrist",
       +"bother",
       +"bottom",
       +"crawl",
       +"crowd",
       +"fix",
       +"forgive",
       +"frown",
       +"grace",
       +"loose",
       +"lucky",
       +"party",
       +"release",
       +"surely",
       +"survive",
       +"teacher",
       +"gently",
       +"grip",
       +"speed",
       +"suicide",
       +"travel",
       +"treat",
       +"vein",
       +"written",
       +"cage",
       +"chain",
       +"conversation",
       +"date",
       +"enemy",
       +"however",
       +"interest",
       +"million",
       +"page",
       +"pink",
       +"proud",
       +"sway",
       +"themselves",
       +"winter",
       +"church",
       +"cruel",
       +"cup",
       +"demon",
       +"experience",
       +"freedom",
       +"pair",
       +"pop",
       +"purpose",
       +"respect",
       +"shoot",
       +"softly",
       +"state",
       +"strange",
       +"bar",
       +"birth",
       +"curl",
       +"dirt",
       +"excuse",
       +"lord",
       +"lovely",
       +"monster",
       +"order",
       +"pack",
       +"pants",
       +"pool",
       +"scene",
       +"seven",
       +"shame",
       +"slide",
       +"ugly",
       +"among",
       +"blade",
       +"blonde",
       +"closet",
       +"creek",
       +"deny",
       +"drug",
       +"eternity",
       +"gain",
       +"grade",
       +"handle",
       +"key",
       +"linger",
       +"pale",
       +"prepare",
       +"swallow",
       +"swim",
       +"tremble",
       +"wheel",
       +"won",
       +"cast",
       +"cigarette",
       +"claim",
       +"college",
       +"direction",
       +"dirty",
       +"gather",
       +"ghost",
       +"hundred",
       +"loss",
       +"lung",
       +"orange",
       +"present",
       +"swear",
       +"swirl",
       +"twice",
       +"wild",
       +"bitter",
       +"blanket",
       +"doctor",
       +"everywhere",
       +"flash",
       +"grown",
       +"knowledge",
       +"numb",
       +"pressure",
       +"radio",
       +"repeat",
       +"ruin",
       +"spend",
       +"unknown",
       +"buy",
       +"clock",
       +"devil",
       +"early",
       +"false",
       +"fantasy",
       +"pound",
       +"precious",
       +"refuse",
       +"sheet",
       +"teeth",
       +"welcome",
       +"add",
       +"ahead",
       +"block",
       +"bury",
       +"caress",
       +"content",
       +"depth",
       +"despite",
       +"distant",
       +"marry",
       +"purple",
       +"threw",
       +"whenever",
       +"bomb",
       +"dull",
       +"easily",
       +"grasp",
       +"hospital",
       +"innocence",
       +"normal",
       +"receive",
       +"reply",
       +"rhyme",
       +"shade",
       +"someday",
       +"sword",
       +"toe",
       +"visit",
       +"asleep",
       +"bought",
       +"center",
       +"consider",
       +"flat",
       +"hero",
       +"history",
       +"ink",
       +"insane",
       +"muscle",
       +"mystery",
       +"pocket",
       +"reflection",
       +"shove",
       +"silently",
       +"smart",
       +"soldier",
       +"spot",
       +"stress",
       +"train",
       +"type",
       +"view",
       +"whether",
       +"bus",
       +"energy",
       +"explain",
       +"holy",
       +"hunger",
       +"inch",
       +"magic",
       +"mix",
       +"noise",
       +"nowhere",
       +"prayer",
       +"presence",
       +"shock",
       +"snap",
       +"spider",
       +"study",
       +"thunder",
       +"trail",
       +"admit",
       +"agree",
       +"bag",
       +"bang",
       +"bound",
       +"butterfly",
       +"cute",
       +"exactly",
       +"explode",
       +"familiar",
       +"fold",
       +"further",
       +"pierce",
       +"reflect",
       +"scent",
       +"selfish",
       +"sharp",
       +"sink",
       +"spring",
       +"stumble",
       +"universe",
       +"weep",
       +"women",
       +"wonderful",
       +"action",
       +"ancient",
       +"attempt",
       +"avoid",
       +"birthday",
       +"branch",
       +"chocolate",
       +"core",
       +"depress",
       +"drunk",
       +"especially",
       +"focus",
       +"fruit",
       +"honest",
       +"match",
       +"palm",
       +"perfectly",
       +"pillow",
       +"pity",
       +"poison",
       +"roar",
       +"shift",
       +"slightly",
       +"thump",
       +"truck",
       +"tune",
       +"twenty",
       +"unable",
       +"wipe",
       +"wrote",
       +"coat",
       +"constant",
       +"dinner",
       +"drove",
       +"egg",
       +"eternal",
       +"flight",
       +"flood",
       +"frame",
       +"freak",
       +"gasp",
       +"glad",
       +"hollow",
       +"motion",
       +"peer",
       +"plastic",
       +"root",
       +"screen",
       +"season",
       +"sting",
       +"strike",
       +"team",
       +"unlike",
       +"victim",
       +"volume",
       +"warn",
       +"weird",
       +"attack",
       +"await",
       +"awake",
       +"built",
       +"charm",
       +"crave",
       +"despair",
       +"fought",
       +"grant",
       +"grief",
       +"horse",
       +"limit",
       +"message",
       +"ripple",
       +"sanity",
       +"scatter",
       +"serve",
       +"split",
       +"string",
       +"trick",
       +"annoy",
       +"blur",
       +"boat",
       +"brave",
       +"clearly",
       +"cling",
       +"connect",
       +"fist",
       +"forth",
       +"imagination",
       +"iron",
       +"jock",
       +"judge",
       +"lesson",
       +"milk",
       +"misery",
       +"nail",
       +"naked",
       +"ourselves",
       +"poet",
       +"possible",
       +"princess",
       +"sail",
       +"size",
       +"snake",
       +"society",
       +"stroke",
       +"torture",
       +"toss",
       +"trace",
       +"wise",
       +"bloom",
       +"bullet",
       +"cell",
       +"check",
       +"cost",
       +"darling",
       +"during",
       +"footstep",
       +"fragile",
       +"hallway",
       +"hardly",
       +"horizon",
       +"invisible",
       +"journey",
       +"midnight",
       +"mud",
       +"nod",
       +"pause",
       +"relax",
       +"shiver",
       +"sudden",
       +"value",
       +"youth",
       +"abuse",
       +"admire",
       +"blink",
       +"breast",
       +"bruise",
       +"constantly",
       +"couple",
       +"creep",
       +"curve",
       +"difference",
       +"dumb",
       +"emptiness",
       +"gotta",
       +"honor",
       +"plain",
       +"planet",
       +"recall",
       +"rub",
       +"ship",
       +"slam",
       +"soar",
       +"somebody",
       +"tightly",
       +"weather",
       +"adore",
       +"approach",
       +"bond",
       +"bread",
       +"burst",
       +"candle",
       +"coffee",
       +"cousin",
       +"crime",
       +"desert",
       +"flutter",
       +"frozen",
       +"grand",
       +"heel",
       +"hello",
       +"language",
       +"level",
       +"movement",
       +"pleasure",
       +"powerful",
       +"random",
       +"rhythm",
       +"settle",
       +"silly",
       +"slap",
       +"sort",
       +"spoken",
       +"steel",
       +"threaten",
       +"tumble",
       +"upset",
       +"aside",
       +"awkward",
       +"bee",
       +"blank",
       +"board",
       +"button",
       +"card",
       +"carefully",
       +"complain",
       +"crap",
       +"deeply",
       +"discover",
       +"drag",
       +"dread",
       +"effort",
       +"entire",
       +"fairy",
       +"giant",
       +"gotten",
       +"greet",
       +"illusion",
       +"jeans",
       +"leap",
       +"liquid",
       +"march",
       +"mend",
       +"nervous",
       +"nine",
       +"replace",
       +"rope",
       +"spine",
       +"stole",
       +"terror",
       +"accident",
       +"apple",
       +"balance",
       +"boom",
       +"childhood",
       +"collect",
       +"demand",
       +"depression",
       +"eventually",
       +"faint",
       +"glare",
       +"goal",
       +"group",
       +"honey",
       +"kitchen",
       +"laid",
       +"limb",
       +"machine",
       +"mere",
       +"mold",
       +"murder",
       +"nerve",
       +"painful",
       +"poetry",
       +"prince",
       +"rabbit",
       +"shelter",
       +"shore",
       +"shower",
       +"soothe",
       +"stair",
       +"steady",
       +"sunlight",
       +"tangle",
       +"tease",
       +"treasure",
       +"uncle",
       +"begun",
       +"bliss",
       +"canvas",
       +"cheer",
       +"claw",
       +"clutch",
       +"commit",
       +"crimson",
       +"crystal",
       +"delight",
       +"doll",
       +"existence",
       +"express",
       +"fog",
       +"football",
       +"gay",
       +"goose",
       +"guard",
       +"hatred",
       +"illuminate",
       +"mass",
       +"math",
       +"mourn",
       +"rich",
       +"rough",
       +"skip",
       +"stir",
       +"student",
       +"style",
       +"support",
       +"thorn",
       +"tough",
       +"yard",
       +"yearn",
       +"yesterday",
       +"advice",
       +"appreciate",
       +"autumn",
       +"bank",
       +"beam",
       +"bowl",
       +"capture",
       +"carve",
       +"collapse",
       +"confusion",
       +"creation",
       +"dove",
       +"feather",
       +"girlfriend",
       +"glory",
       +"government",
       +"harsh",
       +"hop",
       +"inner",
       +"loser",
       +"moonlight",
       +"neighbor",
       +"neither",
       +"peach",
       +"pig",
       +"praise",
       +"screw",
       +"shield",
       +"shimmer",
       +"sneak",
       +"stab",
       +"subject",
       +"throughout",
       +"thrown",
       +"tower",
       +"twirl",
       +"wow",
       +"army",
       +"arrive",
       +"bathroom",
       +"bump",
       +"cease",
       +"cookie",
       +"couch",
       +"courage",
       +"dim",
       +"guilt",
       +"howl",
       +"hum",
       +"husband",
       +"insult",
       +"led",
       +"lunch",
       +"mock",
       +"mostly",
       +"natural",
       +"nearly",
       +"needle",
       +"nerd",
       +"peaceful",
       +"perfection",
       +"pile",
       +"price",
       +"remove",
       +"roam",
       +"sanctuary",
       +"serious",
       +"shiny",
       +"shook",
       +"sob",
       +"stolen",
       +"tap",
       +"vain",
       +"void",
       +"warrior",
       +"wrinkle",
       +"affection",
       +"apologize",
       +"blossom",
       +"bounce",
       +"bridge",
       +"cheap",
       +"crumble",
       +"decision",
       +"descend",
       +"desperately",
       +"dig",
       +"dot",
       +"flip",
       +"frighten",
       +"heartbeat",
       +"huge",
       +"lazy",
       +"lick",
       +"odd",
       +"opinion",
       +"process",
       +"puzzle",
       +"quietly",
       +"retreat",
       +"score",
       +"sentence",
       +"separate",
       +"situation",
       +"skill",
       +"soak",
       +"square",
       +"stray",
       +"taint",
       +"task",
       +"tide",
       +"underneath",
       +"veil",
       +"whistle",
       +"anywhere",
       +"bedroom",
       +"bid",
       +"bloody",
       +"burden",
       +"careful",
       +"compare",
       +"concern",
       +"curtain",
       +"decay",
       +"defeat",
       +"describe",
       +"double",
       +"dreamer",
       +"driver",
       +"dwell",
       +"evening",
       +"flare",
       +"flicker",
       +"grandma",
       +"guitar",
       +"harm",
       +"horrible",
       +"hungry",
       +"indeed",
       +"lace",
       +"melody",
       +"monkey",
       +"nation",
       +"object",
       +"obviously",
       +"rainbow",
       +"salt",
       +"scratch",
       +"shown",
       +"shy",
       +"stage",
       +"stun",
       +"third",
       +"tickle",
       +"useless",
       +"weakness",
       +"worship",
       +"worthless",
       +"afternoon",
       +"beard",
       +"boyfriend",
       +"bubble",
       +"busy",
       +"certain",
       +"chin",
       +"concrete",
       +"desk",
       +"diamond",
       +"doom",
       +"drawn",
       +"due",
       +"felicity",
       +"freeze",
       +"frost",
       +"garden",
       +"glide",
       +"harmony",
       +"hopefully",
       +"hunt",
       +"jealous",
       +"lightning",
       +"mama",
       +"mercy",
       +"peel",
       +"physical",
       +"position",
       +"pulse",
       +"punch",
       +"quit",
       +"rant",
       +"respond",
       +"salty",
       +"sane",
       +"satisfy",
       +"savior",
       +"sheep",
       +"slept",
       +"social",
       +"sport",
       +"tuck",
       +"utter",
       +"valley",
       +"wolf",
       +"aim",
       +"alas",
       +"alter",
       +"arrow",
       +"awaken",
       +"beaten",
       +"belief",
       +"brand",
       +"ceiling",
       +"cheese",
       +"clue",
       +"confidence",
       +"connection",
       +"daily",
       +"disguise",
       +"eager",
       +"erase",
       +"essence",
       +"everytime",
       +"expression",
       +"fan",
       +"flag",
       +"flirt",
       +"foul",
       +"fur",
       +"giggle",
       +"glorious",
       +"ignorance",
       +"law",
       +"lifeless",
       +"measure",
       +"mighty",
       +"muse",
       +"north",
       +"opposite",
       +"paradise",
       +"patience",
       +"patient",
       +"pencil",
       +"petal",
       +"plate",
       +"ponder",
       +"possibly",
       +"practice",
       +"slice",
       +"spell",
       +"stock",
       +"strife",
       +"strip",
       +"suffocate",
       +"suit",
       +"tender",
       +"tool",
       +"trade",
       +"velvet",
       +"verse",
       +"waist",
       +"witch",
       +"aunt",
       +"bench",
       +"bold",
       +"cap",
       +"certainly",
       +"click",
       +"companion",
       +"creator",
       +"dart",
       +"delicate",
       +"determine",
       +"dish",
       +"dragon",
       +"drama",
       +"drum",
       +"dude",
       +"everybody",
       +"feast",
       +"forehead",
       +"former",
       +"fright",
       +"fully",
       +"gas",
       +"hook",
       +"hurl",
       +"invite",
       +"juice",
       +"manage",
       +"moral",
       +"possess",
       +"raw",
       +"rebel",
       +"royal",
       +"scale",
       +"scary",
       +"several",
       +"slight",
       +"stubborn",
       +"swell",
       +"talent",
       +"tea",
       +"terrible",
       +"thread",
       +"torment",
       +"trickle",
       +"usually",
       +"vast",
       +"violence",
       +"weave",
       +"acid",
       +"agony",
       +"ashamed",
       +"awe",
       +"belly",
       +"blend",
       +"blush",
       +"character",
       +"cheat",
       +"common",
       +"company",
       +"coward",
       +"creak",
       +"danger",
       +"deadly",
       +"defense",
       +"define",
       +"depend",
       +"desperate",
       +"destination",
       +"dew",
       +"duck",
       +"dusty",
       +"embarrass",
       +"engine",
       +"example",
       +"explore",
       +"foe",
       +"freely",
       +"frustrate",
       +"generation",
       +"glove",
       +"guilty",
       +"health",
       +"hurry",
       +"idiot",
       +"impossible",
       +"inhale",
       +"jaw",
       +"kingdom",
       +"mention",
       +"mist",
       +"moan",
       +"mumble",
       +"mutter",
       +"observe",
       +"ode",
       +"pathetic",
       +"pattern",
       +"pie",
       +"prefer",
       +"puff",
       +"rape",
       +"rare",
       +"revenge",
       +"rude",
       +"scrape",
       +"spiral",
       +"squeeze",
       +"strain",
       +"sunset",
       +"suspend",
       +"sympathy",
       +"thigh",
       +"throne",
       +"total",
       +"unseen",
       +"weapon",
       +"weary"
       +]
       +
       +
       +
       +n = 1626
       +
       +# Note about US patent no 5892470: Here each word does not represent a given digit.
       +# Instead, the digit represented by a word is variable, it depends on the previous word.
       +
       +def mn_encode( message ):
       +    assert len(message) % 8 == 0
       +    out = []
       +    for i in range(len(message)//8):
       +        word = message[8*i:8*i+8]
       +        x = int(word, 16)
       +        w1 = (x%n)
       +        w2 = ((x//n) + w1)%n
       +        w3 = ((x//n//n) + w2)%n
       +        out += [ words[w1], words[w2], words[w3] ]
       +    return out
       +
       +
       +def mn_decode( wlist ):
       +    out = ''
       +    for i in range(len(wlist)//3):
       +        word1, word2, word3 = wlist[3*i:3*i+3]
       +        w1 =  words.index(word1)
       +        w2 = (words.index(word2))%n
       +        w3 = (words.index(word3))%n
       +        x = w1 +n*((w2-w1)%n) +n*n*((w3-w2)%n)
       +        out += '%08x'%x
       +    return out
       +
       +
       +if __name__ == '__main__':
       +    import sys
       +    if len(sys.argv) == 1:
       +        print('I need arguments: a hex string to encode, or a list of words to decode')
       +    elif len(sys.argv) == 2:
       +        print(' '.join(mn_encode(sys.argv[1])))
       +    else:
       +        print(mn_decode(sys.argv[1:]))
   DIR diff --git a/electrum/paymentrequest.proto b/electrum/paymentrequest.proto
       t@@ -0,0 +1,47 @@
       +//
       +// Simple Bitcoin Payment Protocol messages
       +//
       +// Use fields 1000+ for extensions;
       +// to avoid conflicts, register extensions via pull-req at
       +// https://github.com/bitcoin/bips/bip-0070/extensions.mediawiki
       +//
       +
       +syntax = "proto2";
       +package payments;
       +option java_package = "org.bitcoin.protocols.payments";
       +option java_outer_classname = "Protos";
       +
       +// Generalized form of "send payment to this/these bitcoin addresses"
       +message Output {
       +        optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis
       +        required bytes script = 2; // usually one of the standard Script forms
       +}
       +message PaymentDetails {
       +        optional string network = 1 [default = "main"]; // "main" or "test"
       +        repeated Output outputs = 2;        // Where payment should be sent
       +        required uint64 time = 3;           // Timestamp; when payment request created
       +        optional uint64 expires = 4;        // Timestamp; when this request should be considered invalid
       +        optional string memo = 5;           // Human-readable description of request for the customer
       +        optional string payment_url = 6;    // URL to send Payment and get PaymentACK
       +        optional bytes merchant_data = 7;   // Arbitrary data to include in the Payment message
       +}
       +message PaymentRequest {
       +        optional uint32 payment_details_version = 1 [default = 1];
       +        optional string pki_type = 2 [default = "none"];  // none / x509+sha256 / x509+sha1
       +        optional bytes pki_data = 3;                      // depends on pki_type
       +        required bytes serialized_payment_details = 4;    // PaymentDetails
       +        optional bytes signature = 5;                     // pki-dependent signature
       +}
       +message X509Certificates {
       +        repeated bytes certificate = 1;    // DER-encoded X.509 certificate chain
       +}
       +message Payment {
       +        optional bytes merchant_data = 1;  // From PaymentDetails.merchant_data
       +        repeated bytes transactions = 2;   // Signed transactions that satisfy PaymentDetails.outputs
       +        repeated Output refund_to = 3;     // Where to send refunds, if a refund is necessary
       +        optional string memo = 4;          // Human-readable message for the merchant
       +}
       +message PaymentACK {
       +        required Payment payment = 1;      // Payment message that triggered this ACK
       +        optional string memo = 2;          // human-readable message for customer
       +}
   DIR diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py
       t@@ -0,0 +1,523 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2014 Thomas Voegtlin
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +import hashlib
       +import sys
       +import time
       +import traceback
       +import json
       +import requests
       +
       +import urllib.parse
       +
       +
       +try:
       +    from . import paymentrequest_pb2 as pb2
       +except ImportError:
       +    sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto'")
       +
       +from . import bitcoin, ecc, util, transaction, x509, rsakey
       +from .util import print_error, bh2u, bfh
       +from .util import export_meta, import_meta
       +
       +from .bitcoin import TYPE_ADDRESS
       +
       +REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
       +ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
       +
       +ca_path = requests.certs.where()
       +ca_list = None
       +ca_keyID = None
       +
       +def load_ca_list():
       +    global ca_list, ca_keyID
       +    if ca_list is None:
       +        ca_list, ca_keyID = x509.load_certificates(ca_path)
       +
       +
       +
       +# status of payment requests
       +PR_UNPAID  = 0
       +PR_EXPIRED = 1
       +PR_UNKNOWN = 2     # sent but not propagated
       +PR_PAID    = 3     # send and propagated
       +
       +
       +
       +def get_payment_request(url):
       +    u = urllib.parse.urlparse(url)
       +    error = None
       +    if u.scheme in ['http', 'https']:
       +        try:
       +            response = requests.request('GET', url, headers=REQUEST_HEADERS)
       +            response.raise_for_status()
       +            # Guard against `bitcoin:`-URIs with invalid payment request URLs
       +            if "Content-Type" not in response.headers \
       +            or response.headers["Content-Type"] != "application/bitcoin-paymentrequest":
       +                data = None
       +                error = "payment URL not pointing to a payment request handling server"
       +            else:
       +                data = response.content
       +            print_error('fetched payment request', url, len(response.content))
       +        except requests.exceptions.RequestException:
       +            data = None
       +            error = "payment URL not pointing to a valid server"
       +    elif u.scheme == 'file':
       +        try:
       +            with open(u.path, 'r', encoding='utf-8') as f:
       +                data = f.read()
       +        except IOError:
       +            data = None
       +            error = "payment URL not pointing to a valid file"
       +    else:
       +        raise Exception("unknown scheme", url)
       +    pr = PaymentRequest(data, error)
       +    return pr
       +
       +
       +class PaymentRequest:
       +
       +    def __init__(self, data, error=None):
       +        self.raw = data
       +        self.error = error
       +        self.parse(data)
       +        self.requestor = None # known after verify
       +        self.tx = None
       +
       +    def __str__(self):
       +        return str(self.raw)
       +
       +    def parse(self, r):
       +        if self.error:
       +            return
       +        self.id = bh2u(bitcoin.sha256(r)[0:16])
       +        try:
       +            self.data = pb2.PaymentRequest()
       +            self.data.ParseFromString(r)
       +        except:
       +            self.error = "cannot parse payment request"
       +            return
       +        self.details = pb2.PaymentDetails()
       +        self.details.ParseFromString(self.data.serialized_payment_details)
       +        self.outputs = []
       +        for o in self.details.outputs:
       +            addr = transaction.get_address_from_output_script(o.script)[1]
       +            self.outputs.append((TYPE_ADDRESS, addr, o.amount))
       +        self.memo = self.details.memo
       +        self.payment_url = self.details.payment_url
       +
       +    def is_pr(self):
       +        return self.get_amount() != 0
       +        #return self.get_outputs() != [(TYPE_ADDRESS, self.get_requestor(), self.get_amount())]
       +
       +    def verify(self, contacts):
       +        if self.error:
       +            return False
       +        if not self.raw:
       +            self.error = "Empty request"
       +            return False
       +        pr = pb2.PaymentRequest()
       +        try:
       +            pr.ParseFromString(self.raw)
       +        except:
       +            self.error = "Error: Cannot parse payment request"
       +            return False
       +        if not pr.signature:
       +            # the address will be displayed as requestor
       +            self.requestor = None
       +            return True
       +        if pr.pki_type in ["x509+sha256", "x509+sha1"]:
       +            return self.verify_x509(pr)
       +        elif pr.pki_type in ["dnssec+btc", "dnssec+ecdsa"]:
       +            return self.verify_dnssec(pr, contacts)
       +        else:
       +            self.error = "ERROR: Unsupported PKI Type for Message Signature"
       +            return False
       +
       +    def verify_x509(self, paymntreq):
       +        load_ca_list()
       +        if not ca_list:
       +            self.error = "Trusted certificate authorities list not found"
       +            return False
       +        cert = pb2.X509Certificates()
       +        cert.ParseFromString(paymntreq.pki_data)
       +        # verify the chain of certificates
       +        try:
       +            x, ca = verify_cert_chain(cert.certificate)
       +        except BaseException as e:
       +            traceback.print_exc(file=sys.stderr)
       +            self.error = str(e)
       +            return False
       +        # get requestor name
       +        self.requestor = x.get_common_name()
       +        if self.requestor.startswith('*.'):
       +            self.requestor = self.requestor[2:]
       +        # verify the BIP70 signature
       +        pubkey0 = rsakey.RSAKey(x.modulus, x.exponent)
       +        sig = paymntreq.signature
       +        paymntreq.signature = b''
       +        s = paymntreq.SerializeToString()
       +        sigBytes = bytearray(sig)
       +        msgBytes = bytearray(s)
       +        if paymntreq.pki_type == "x509+sha256":
       +            hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
       +            verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes)
       +        elif paymntreq.pki_type == "x509+sha1":
       +            verify = pubkey0.hashAndVerify(sigBytes, msgBytes)
       +        if not verify:
       +            self.error = "ERROR: Invalid Signature for Payment Request Data"
       +            return False
       +        ### SIG Verified
       +        self.error = 'Signed by Trusted CA: ' + ca.get_common_name()
       +        return True
       +
       +    def verify_dnssec(self, pr, contacts):
       +        sig = pr.signature
       +        alias = pr.pki_data
       +        info = contacts.resolve(alias)
       +        if info.get('validated') is not True:
       +            self.error = "Alias verification failed (DNSSEC)"
       +            return False
       +        if pr.pki_type == "dnssec+btc":
       +            self.requestor = alias
       +            address = info.get('address')
       +            pr.signature = b''
       +            message = pr.SerializeToString()
       +            if ecc.verify_message_with_address(address, sig, message):
       +                self.error = 'Verified with DNSSEC'
       +                return True
       +            else:
       +                self.error = "verify failed"
       +                return False
       +        else:
       +            self.error = "unknown algo"
       +            return False
       +
       +    def has_expired(self):
       +        return self.details.expires and self.details.expires < int(time.time())
       +
       +    def get_expiration_date(self):
       +        return self.details.expires
       +
       +    def get_amount(self):
       +        return sum(map(lambda x:x[2], self.outputs))
       +
       +    def get_address(self):
       +        o = self.outputs[0]
       +        assert o[0] == TYPE_ADDRESS
       +        return o[1]
       +
       +    def get_requestor(self):
       +        return self.requestor if self.requestor else self.get_address()
       +
       +    def get_verify_status(self):
       +        return self.error if self.requestor else "No Signature"
       +
       +    def get_memo(self):
       +        return self.memo
       +
       +    def get_dict(self):
       +        return {
       +            'requestor': self.get_requestor(),
       +            'memo':self.get_memo(),
       +            'exp': self.get_expiration_date(),
       +            'amount': self.get_amount(),
       +            'signature': self.get_verify_status(),
       +            'txid': self.tx,
       +            'outputs': self.get_outputs()
       +        }
       +
       +    def get_id(self):
       +        return self.id if self.requestor else self.get_address()
       +
       +    def get_outputs(self):
       +        return self.outputs[:]
       +
       +    def send_ack(self, raw_tx, refund_addr):
       +        pay_det = self.details
       +        if not self.details.payment_url:
       +            return False, "no url"
       +        paymnt = pb2.Payment()
       +        paymnt.merchant_data = pay_det.merchant_data
       +        paymnt.transactions.append(bfh(raw_tx))
       +        ref_out = paymnt.refund_to.add()
       +        ref_out.script = util.bfh(transaction.Transaction.pay_script(TYPE_ADDRESS, refund_addr))
       +        paymnt.memo = "Paid using Electrum"
       +        pm = paymnt.SerializeToString()
       +        payurl = urllib.parse.urlparse(pay_det.payment_url)
       +        try:
       +            r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=ca_path)
       +        except requests.exceptions.SSLError:
       +            print("Payment Message/PaymentACK verify Failed")
       +            try:
       +                r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=False)
       +            except Exception as e:
       +                print(e)
       +                return False, "Payment Message/PaymentACK Failed"
       +        if r.status_code >= 500:
       +            return False, r.reason
       +        try:
       +            paymntack = pb2.PaymentACK()
       +            paymntack.ParseFromString(r.content)
       +        except Exception:
       +            return False, "PaymentACK could not be processed. Payment was sent; please manually verify that payment was received."
       +        print("PaymentACK message received: %s" % paymntack.memo)
       +        return True, paymntack.memo
       +
       +
       +def make_unsigned_request(req):
       +    from .transaction import Transaction
       +    addr = req['address']
       +    time = req.get('time', 0)
       +    exp = req.get('exp', 0)
       +    if time and type(time) != int:
       +        time = 0
       +    if exp and type(exp) != int:
       +        exp = 0
       +    amount = req['amount']
       +    if amount is None:
       +        amount = 0
       +    memo = req['memo']
       +    script = bfh(Transaction.pay_script(TYPE_ADDRESS, addr))
       +    outputs = [(script, amount)]
       +    pd = pb2.PaymentDetails()
       +    for script, amount in outputs:
       +        pd.outputs.add(amount=amount, script=script)
       +    pd.time = time
       +    pd.expires = time + exp if exp else 0
       +    pd.memo = memo
       +    pr = pb2.PaymentRequest()
       +    pr.serialized_payment_details = pd.SerializeToString()
       +    pr.signature = util.to_bytes('')
       +    return pr
       +
       +
       +def sign_request_with_alias(pr, alias, alias_privkey):
       +    pr.pki_type = 'dnssec+btc'
       +    pr.pki_data = str(alias)
       +    message = pr.SerializeToString()
       +    ec_key = ecc.ECPrivkey(alias_privkey)
       +    compressed = bitcoin.is_compressed(alias_privkey)
       +    pr.signature = ec_key.sign_message(message, compressed)
       +
       +
       +def verify_cert_chain(chain):
       +    """ Verify a chain of certificates. The last certificate is the CA"""
       +    load_ca_list()
       +    # parse the chain
       +    cert_num = len(chain)
       +    x509_chain = []
       +    for i in range(cert_num):
       +        x = x509.X509(bytearray(chain[i]))
       +        x509_chain.append(x)
       +        if i == 0:
       +            x.check_date()
       +        else:
       +            if not x.check_ca():
       +                raise Exception("ERROR: Supplied CA Certificate Error")
       +    if not cert_num > 1:
       +        raise Exception("ERROR: CA Certificate Chain Not Provided by Payment Processor")
       +    # if the root CA is not supplied, add it to the chain
       +    ca = x509_chain[cert_num-1]
       +    if ca.getFingerprint() not in ca_list:
       +        keyID = ca.get_issuer_keyID()
       +        f = ca_keyID.get(keyID)
       +        if f:
       +            root = ca_list[f]
       +            x509_chain.append(root)
       +        else:
       +            raise Exception("Supplied CA Not Found in Trusted CA Store.")
       +    # verify the chain of signatures
       +    cert_num = len(x509_chain)
       +    for i in range(1, cert_num):
       +        x = x509_chain[i]
       +        prev_x = x509_chain[i-1]
       +        algo, sig, data = prev_x.get_signature()
       +        sig = bytearray(sig)
       +        pubkey = rsakey.RSAKey(x.modulus, x.exponent)
       +        if algo == x509.ALGO_RSA_SHA1:
       +            verify = pubkey.hashAndVerify(sig, data)
       +        elif algo == x509.ALGO_RSA_SHA256:
       +            hashBytes = bytearray(hashlib.sha256(data).digest())
       +            verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA256 + hashBytes)
       +        elif algo == x509.ALGO_RSA_SHA384:
       +            hashBytes = bytearray(hashlib.sha384(data).digest())
       +            verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA384 + hashBytes)
       +        elif algo == x509.ALGO_RSA_SHA512:
       +            hashBytes = bytearray(hashlib.sha512(data).digest())
       +            verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA512 + hashBytes)
       +        else:
       +            raise Exception("Algorithm not supported")
       +            util.print_error(self.error, algo.getComponentByName('algorithm'))
       +        if not verify:
       +            raise Exception("Certificate not Signed by Provided CA Certificate Chain")
       +
       +    return x509_chain[0], ca
       +
       +
       +def check_ssl_config(config):
       +    from . import pem
       +    key_path = config.get('ssl_privkey')
       +    cert_path = config.get('ssl_chain')
       +    with open(key_path, 'r', encoding='utf-8') as f:
       +        params = pem.parse_private_key(f.read())
       +    with open(cert_path, 'r', encoding='utf-8') as f:
       +        s = f.read()
       +    bList = pem.dePemList(s, "CERTIFICATE")
       +    # verify chain
       +    x, ca = verify_cert_chain(bList)
       +    # verify that privkey and pubkey match
       +    privkey = rsakey.RSAKey(*params)
       +    pubkey = rsakey.RSAKey(x.modulus, x.exponent)
       +    assert x.modulus == params[0]
       +    assert x.exponent == params[1]
       +    # return requestor
       +    requestor = x.get_common_name()
       +    if requestor.startswith('*.'):
       +        requestor = requestor[2:]
       +    return requestor
       +
       +def sign_request_with_x509(pr, key_path, cert_path):
       +    from . import pem
       +    with open(key_path, 'r', encoding='utf-8') as f:
       +        params = pem.parse_private_key(f.read())
       +        privkey = rsakey.RSAKey(*params)
       +    with open(cert_path, 'r', encoding='utf-8') as f:
       +        s = f.read()
       +        bList = pem.dePemList(s, "CERTIFICATE")
       +    certificates = pb2.X509Certificates()
       +    certificates.certificate.extend(map(bytes, bList))
       +    pr.pki_type = 'x509+sha256'
       +    pr.pki_data = certificates.SerializeToString()
       +    msgBytes = bytearray(pr.SerializeToString())
       +    hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
       +    sig = privkey.sign(x509.PREFIX_RSA_SHA256 + hashBytes)
       +    pr.signature = bytes(sig)
       +
       +
       +def serialize_request(req):
       +    pr = make_unsigned_request(req)
       +    signature = req.get('sig')
       +    requestor = req.get('name')
       +    if requestor and signature:
       +        pr.signature = bfh(signature)
       +        pr.pki_type = 'dnssec+btc'
       +        pr.pki_data = str(requestor)
       +    return pr
       +
       +
       +def make_request(config, req):
       +    pr = make_unsigned_request(req)
       +    key_path = config.get('ssl_privkey')
       +    cert_path = config.get('ssl_chain')
       +    if key_path and cert_path:
       +        sign_request_with_x509(pr, key_path, cert_path)
       +    return pr
       +
       +
       +
       +class InvoiceStore(object):
       +
       +    def __init__(self, storage):
       +        self.storage = storage
       +        self.invoices = {}
       +        self.paid = {}
       +        d = self.storage.get('invoices', {})
       +        self.load(d)
       +
       +    def set_paid(self, pr, txid):
       +        pr.tx = txid
       +        pr_id = pr.get_id()
       +        self.paid[txid] = pr_id
       +        if pr_id not in self.invoices:
       +            # in case the user had deleted it previously
       +            self.add(pr)
       +
       +    def load(self, d):
       +        for k, v in d.items():
       +            try:
       +                pr = PaymentRequest(bfh(v.get('hex')))
       +                pr.tx = v.get('txid')
       +                pr.requestor = v.get('requestor')
       +                self.invoices[k] = pr
       +                if pr.tx:
       +                    self.paid[pr.tx] = k
       +            except:
       +                continue
       +
       +    def import_file(self, path):
       +        def validate(data):
       +            return data  # TODO
       +        import_meta(path, validate, self.on_import)
       +
       +    def on_import(self, data):
       +        self.load(data)
       +        self.save()
       +
       +    def export_file(self, filename):
       +        export_meta(self.dump(), filename)
       +
       +    def dump(self):
       +        d = {}
       +        for k, pr in self.invoices.items():
       +            d[k] = {
       +                'hex': bh2u(pr.raw),
       +                'requestor': pr.requestor,
       +                'txid': pr.tx
       +            }
       +        return d
       +
       +    def save(self):
       +        self.storage.put('invoices', self.dump())
       +
       +    def get_status(self, key):
       +        pr = self.get(key)
       +        if pr is None:
       +            print_error("[InvoiceStore] get_status() can't find pr for", key)
       +            return
       +        if pr.tx is not None:
       +            return PR_PAID
       +        if pr.has_expired():
       +            return PR_EXPIRED
       +        return PR_UNPAID
       +
       +    def add(self, pr):
       +        key = pr.get_id()
       +        self.invoices[key] = pr
       +        self.save()
       +        return key
       +
       +    def remove(self, key):
       +        self.invoices.pop(key)
       +        self.save()
       +
       +    def get(self, k):
       +        return self.invoices.get(k)
       +
       +    def sorted_list(self):
       +        # sort
       +        return self.invoices.values()
       +
       +    def unpaid_invoices(self):
       +        return [ self.invoices[k] for k in filter(lambda x: self.get_status(x)!=PR_PAID, self.invoices.keys())]
   DIR diff --git a/electrum/paymentrequest_pb2.py b/electrum/paymentrequest_pb2.py
       t@@ -0,0 +1,367 @@
       +# Generated by the protocol buffer compiler.  DO NOT EDIT!
       +# source: paymentrequest.proto
       +
       +import sys
       +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
       +from google.protobuf import descriptor as _descriptor
       +from google.protobuf import message as _message
       +from google.protobuf import reflection as _reflection
       +from google.protobuf import symbol_database as _symbol_database
       +from google.protobuf import descriptor_pb2
       +# @@protoc_insertion_point(imports)
       +
       +_sym_db = _symbol_database.Default()
       +
       +
       +
       +
       +DESCRIPTOR = _descriptor.FileDescriptor(
       +  name='paymentrequest.proto',
       +  package='payments',
parazyd.org:70 /git/electrum/commit/097ac144d976eb46dff809e1809783dc78ab6d8b.gph:20797: line too long