Example #1
0
 def on_restore_wallet(self, wallet, wizard):
     assert isinstance(wallet, self.wallet_class)
     msg = _("Enter the seed for your %s wallet:" % self.device)
     f = lambda x: wizard.run("on_restore_seed", x)
     wizard.enter_seed_dialog(
         run_next=f, title=_("Restore hardware wallet"), message=msg, is_valid=self.is_valid_seed
     )
Example #2
0
    def context_menu(self):
        view_menu = QMenu()
        themes_menu = view_menu.addMenu(_("&Themes"))
        selected_theme = self.actuator.selected_theme()
        theme_group = QActionGroup(self)
        for theme_name in self.actuator.theme_names():
            theme_action = themes_menu.addAction(theme_name)
            theme_action.setCheckable(True)
            if selected_theme == theme_name:
                theme_action.setChecked(True)
            class SelectThemeFunctor:
                def __init__(self, theme_name, toggle_theme):
                    self.theme_name = theme_name
                    self.toggle_theme = toggle_theme
                def __call__(self, checked):
                    if checked:
                        self.toggle_theme(self.theme_name)
            delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
            theme_action.toggled.connect(delegate)
            theme_group.addAction(theme_action)
        view_menu.addSeparator()

        show_receiving = view_menu.addAction(_("Show Receiving addresses"))
        show_receiving.setCheckable(True)
        show_receiving.toggled.connect(self.toggle_receiving_layout)
        show_receiving.setChecked(self.config.get("gui_show_receiving",False))

        show_history = view_menu.addAction(_("Show History"))
        show_history.setCheckable(True)
        show_history.toggled.connect(self.show_history)
        show_history.setChecked(self.config.get("gui_show_history",False))

        return view_menu
Example #3
0
 def build_tray_menu(self):
     m = QMenu()
     m.addAction(_("Show/Hide"), self.show_or_hide)
     m.addAction(_("Dark/Light"), self.toggle_tray_icon)
     m.addSeparator()
     m.addAction(_("Exit Electrum"), self.close)
     self.tray.setContextMenu(m)
Example #4
0
    def on_verify_restore_ok(self, wallet, _dlg, btn, restore=False):
        if btn in (_dlg.ids.back, _dlg.ids.but_close) :
            _dlg.close()
            Factory.CreateRestoreDialog(
                on_release=self.on_creatrestore_complete).open()
            return

        seed = self.get_seed_text(_dlg.ids.text_input_seed)
        if not seed:
            return app.show_error(_("No seed!"), duration=.5)

        _dlg.close()

        if Wallet.is_seed(seed):
            return self.password_dialog(wallet=wallet, mode='restore',
                                        seed=seed)
        elif Wallet.is_xpub(seed):
            wallet = Wallet.from_xpub(seed, self.storage)
        elif Wallet.is_address(seed):
            wallet = Wallet.from_address(seed, self.storage)
        elif Wallet.is_private_key(seed):
            wallet = Wallet.from_private_key(seed, self.storage)
        else:
            return app.show_error(_('Not a valid seed. App will now exit'),
                                  exit=True, modal=True, duration=.5)
        return
Example #5
0
    def __init__(self, transaction_id, parent):
        super(TransactionWindow, self).__init__()

        self.tx_id = str(transaction_id)
        self.parent = parent

        self.setModal(True)
        self.resize(200,100)
        self.setWindowTitle(_("Transaction successfully sent"))

        self.layout = QGridLayout(self)
        history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
        self.layout.addWidget(QLabel(history_label))

        self.label_edit = QLineEdit()
        self.label_edit.setPlaceholderText(_("Transaction label"))
        self.label_edit.setObjectName("label_input")
        self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
        self.label_edit.setFocusPolicy(Qt.ClickFocus)
        self.layout.addWidget(self.label_edit)

        self.save_button = QPushButton(_("Save"))
        self.layout.addWidget(self.save_button)
        self.save_button.clicked.connect(self.set_label)

        self.exec_()
Example #6
0
    def add_io(self, vbox):

        if self.tx.locktime > 0:
            vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))

        vbox.addWidget(QLabel(_("Inputs")))
        def format_input(x):
            if x.get('is_coinbase'):
                return 'coinbase'
            else:
                _hash = x.get('prevout_hash')
                return _hash[0:16] + '...' + _hash[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
        lines = map(format_input, self.tx.inputs )
        i_text = QTextEdit()
        i_text.setText('\n'.join(lines))
        i_text.setReadOnly(True)
        i_text.setMaximumHeight(100)
        vbox.addWidget(i_text)

        vbox.addWidget(QLabel(_("Outputs")))
        lines = map(lambda x: x[0] + u'\t\t' + self.parent.format_amount(x[1]), self.tx.outputs)
        o_text = QTextEdit()
        o_text.setText('\n'.join(lines))
        o_text.setReadOnly(True)
        o_text.setMaximumHeight(100)
        vbox.addWidget(o_text)
Example #7
0
 def load_wallet(self, wallet):
     self.wallet = wallet
     if self.trezor_is_connected():
         if not self.wallet.check_proper_device():
             QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
     else:
         QMessageBox.information(self.window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode."), _('OK'))
Example #8
0
 def close(self):
     if not self.saved:
         if QMessageBox.question(
                 self, _('Message'), _('This transaction is not saved. Close anyway?'),
                 QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.No:
             return
     QWidget.close(self)
Example #9
0
 def save(self):
     name = 'signed_%s.txn' % (self.tx.hash()[0:8]) if self.tx.is_complete else 'unsigned.txn'
     fileName = self.parent.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 saved successfully"))
Example #10
0
    def mouseReleaseEvent(self, event):
        dialog = QDialog(self)
        dialog.setWindowTitle(_('Electrum update'))
        dialog.setModal(1)

        main_layout = QGridLayout()
        main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)

        ignore_version = QPushButton(_("Ignore this version"))
        ignore_version.clicked.connect(self.ignore_this_version)

        ignore_all_versions = QPushButton(_("Ignore all versions"))
        ignore_all_versions.clicked.connect(self.ignore_all_version)

        open_website = QPushButton(_("Goto download page"))
        open_website.clicked.connect(self.open_website)

        main_layout.addWidget(ignore_version, 1, 0)
        main_layout.addWidget(ignore_all_versions, 1, 1)
        main_layout.addWidget(open_website, 1, 2)

        dialog.setLayout(main_layout)

        self.dialog = dialog

        if not dialog.exec_(): return
Example #11
0
    def do_schedule(self):
        r = self.win.read_send_tab()
        now = QDateTime.currentDateTime()
        t = now.secsTo(self.time_e.dateTime())
        outputs, fee, label, coins = r
        #print type(r), "\n", outputs, "\n", fee, "\n", label, "\n", coins
        if not r:
            return
        if t <= 240:
            QMessageBox.warning(None, _('Error'), _("Please select a time at least five minutes later than current time"), _('OK'))
        return
        time = self.time_e.dateTime().toTime_t()
        amount = self.win.amount_e.get_amount()
        fee = self.win.fee_e.get_amount()
        addr = self.win.payto_e.get_outputs()[0][1]
        self.schedule_requests = self.wallet.storage.get('schedule_requests',{}) 
        self.schedule_requests[time] = (amount, fee, addr)
        self.wallet.storage.put('schedule_requests', self.schedule_requests)
        self.update_schedule_table()

        self.conn = sqlite3.connect('schedule.db')
        c = conn.cursor()
        c.execute("DROP TABLE IF EXISTS list;")
        c.execute("CREATE TABLE IF NOT EXISTS list (timestamp, amount, fee, address, info);")
        c.execute("INSERT INTO list VALUES (?, ?, ?, ?, ?);", (time, amount, fee, address))
        #c.commit()
        conn.close()
Example #12
0
 def value_str(self, satoshis, rate):
     if satoshis is None:  # Can happen with incomplete history
         return _("Unknown")
     if rate:
         value = Decimal(satoshis) / COIN * Decimal(rate)
         return "%s" % (self.ccy_amount_str(value, True))
     return _("No data")
Example #13
0
    def __init__(self, parent):
        super(MatrixDialog, self).__init__(parent)
        self.setWindowTitle(_("Trezor Matrix Recovery"))
        self.num = 9
        self.loop = QEventLoop()

        vbox = QVBoxLayout(self)
        vbox.addWidget(WWLabel(MATRIX_RECOVERY))

        grid = QGridLayout()
        grid.setSpacing(0)
        self.char_buttons = []
        for y in range(3):
            for x in range(3):
                button = QPushButton('?')
                button.clicked.connect(partial(self.process_key, ord('1') + y * 3 + x))
                grid.addWidget(button, 3 - y, x)
                self.char_buttons.append(button)
        vbox.addLayout(grid)

        self.backspace_button = QPushButton("<=")
        self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace))
        self.cancel_button = QPushButton(_("Cancel"))
        self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape))
        buttons = Buttons(self.backspace_button, self.cancel_button)
        vbox.addSpacing(40)
        vbox.addLayout(buttons)
        self.refresh()
        self.show()
Example #14
0
 def get_library_not_available_message(self) -> str:
     if hasattr(self, 'libraries_available_message'):
         message = self.libraries_available_message
     else:
         message = _("Missing libraries for {}.").format(self.name)
     message += '\n' + _("Make sure you install it with python3")
     return message
Example #15
0
    def __init__(self, parent):
        super(CharacterDialog, self).__init__(parent)
        self.setWindowTitle(_("KeepKey Seed Recovery"))
        self.character_pos = 0
        self.word_pos = 0
        self.loop = QEventLoop()
        self.word_help = QLabel()
        self.char_buttons = []

        vbox = QVBoxLayout(self)
        vbox.addWidget(WWLabel(CHARACTER_RECOVERY))
        hbox = QHBoxLayout()
        hbox.addWidget(self.word_help)
        for i in range(4):
            char_button = CharacterButton('*')
            char_button.setMaximumWidth(36)
            self.char_buttons.append(char_button)
            hbox.addWidget(char_button)
        self.accept_button = CharacterButton(_("Accept Word"))
        self.accept_button.clicked.connect(partial(self.process_key, 32))
        self.rejected.connect(partial(self.loop.exit, 1))
        hbox.addWidget(self.accept_button)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        self.finished_button = QPushButton(_("Seed Entered"))
        self.cancel_button = QPushButton(_("Cancel"))
        self.finished_button.clicked.connect(partial(self.process_key,
                                                     Qt.Key_Return))
        self.cancel_button.clicked.connect(self.rejected)
        buttons = Buttons(self.finished_button, self.cancel_button)
        vbox.addSpacing(40)
        vbox.addLayout(buttons)
        self.refresh()
        self.show()
Example #16
0
    def update(self):
        self.menu_actions = [(_("Pay"), self.do_pay), (_("Delete"), self.do_delete)]
        invoices_list = self.screen.ids.invoices_container
        invoices_list.clear_widgets()

        _list = self.app.invoices.sorted_list()
        for pr in _list:
            ci = Factory.InvoiceItem()
            ci.key = pr.get_id()
            ci.requestor = pr.get_requestor()
            ci.memo = pr.memo
            ci.amount = self.app.format_amount_and_units(pr.get_amount())
            status = self.app.invoices.get_status(ci.key)
            if status == PR_PAID:
                ci.icon = "atlas://gui/kivy/theming/light/confirmed"
            elif status == PR_EXPIRED:
                ci.icon = "atlas://gui/kivy/theming/light/important"
            else:
                ci.icon = "atlas://gui/kivy/theming/light/important"
            exp = pr.get_expiration_date()
            ci.date = format_time(exp) if exp else _("Never")
            ci.screen = self
            invoices_list.add_widget(ci)

        if not _list:
            msg = _("This screen shows the list of payment requests that have been sent to you.")
            invoices_list.add_widget(EmptyLabel(text=msg))
Example #17
0
    def update(self):

        self.menu_actions = [(_("Show"), self.do_show), (_("Delete"), self.do_delete)]
        requests_list = self.screen.ids.requests_container
        requests_list.clear_widgets()
        _list = self.app.wallet.get_sorted_requests(self.app.electrum_config)
        for req in _list:
            address = req["address"]
            timestamp = req.get("time", 0)
            amount = req.get("amount")
            expiration = req.get("exp", None)
            status = req.get("status")
            signature = req.get("sig")
            ci = Factory.RequestItem()
            ci.address = req["address"]
            ci.memo = self.app.wallet.get_label(address)
            status = req.get("status")
            if status == PR_PAID:
                ci.icon = "atlas://gui/kivy/theming/light/confirmed"
            elif status == PR_EXPIRED:
                ci.icon = "atlas://gui/kivy/theming/light/important"
            else:
                ci.icon = "atlas://gui/kivy/theming/light/important"
            ci.amount = self.app.format_amount_and_units(amount) if amount else ""
            ci.date = format_time(timestamp)
            ci.screen = self
            requests_list.add_widget(ci)

        if not _list:
            msg = _("This screen shows the list of payment requests you made.")
            requests_list.add_widget(EmptyLabel(text=msg))
Example #18
0
 def delete_alias(self, x):
     if self.gui.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
         if x in self.aliases:
             self.aliases.pop(x)
             self.update_history_tab()
             self.update_contacts_tab()
             self.update_completions()
Example #19
0
    def parse_history(self, items):
        for item in items:
            tx_hash, conf, value, timestamp, balance = item
            time_str = _("unknown")
            if conf > 0:
                try:
                    time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(" ")[:-3]
                except Exception:
                    time_str = _("error")
            if conf == -1:
                time_str = _("unverified")
                icon = "atlas://gui/kivy/theming/light/close"
            elif conf == 0:
                time_str = _("pending")
                icon = "atlas://gui/kivy/theming/light/unconfirmed"
            elif conf < 6:
                conf = max(1, conf)
                icon = "atlas://gui/kivy/theming/light/clock{}".format(conf)
            else:
                icon = "atlas://gui/kivy/theming/light/confirmed"

            label = self.app.wallet.get_label(tx_hash) if tx_hash else _("Pruned transaction outputs")
            date = timestamp_to_datetime(timestamp)
            rate = run_hook("history_rate", date)
            if self.app.fiat_unit:
                s = run_hook("historical_value_str", value, date)
                quote_text = "..." if s is None else s + " " + self.app.fiat_unit
            else:
                quote_text = ""
            yield (conf, icon, time_str, label, value, tx_hash, quote_text)
Example #20
0
 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)
Example #21
0
 def create_contact_menu(self, menu, item):
     label = unicode(item.text(1))
     if label in self.aliases.keys():
         addr = unicode(item.text(0))
         label = unicode(item.text(1))
         menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
         menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
Example #22
0
    def multi_mpk_dialog(self, xpub_hot, n):
        vbox = QVBoxLayout()
        scroll = QScrollArea()
        scroll.setEnabled(True)
        scroll.setWidgetResizable(True)
        vbox.addWidget(scroll)

        w = QWidget()
        scroll.setWidget(w)

        innerVbox = QVBoxLayout()
        w.setLayout(innerVbox)

        vbox0 = seed_dialog.show_seed_box(MSG_SHOW_MPK, xpub_hot, 'hot')
        innerVbox.addLayout(vbox0)
        entries = []
        for i in range(n):
            msg = _("Please enter the master public key of cosigner") + ' %d'%(i+1)
            vbox2, seed_e2 = seed_dialog.enter_seed_box(msg, self, 'cold')
            innerVbox.addLayout(vbox2)
            entries.append(seed_e2)
        vbox.addStretch(1)
        button = OkButton(self, _('Next'))
        vbox.addLayout(Buttons(CancelButton(self), button))
        button.setEnabled(False)
        f = lambda: button.setEnabled( map(lambda e: Wallet.is_xpub(self.get_seed_text(e)), entries) == [True]*len(entries))
        for e in entries:
            e.textChanged.connect(f)
        self.set_layout(vbox)
        if not self.exec_():
            return
        return map(lambda e: self.get_seed_text(e), entries)
Example #23
0
    def password_dialog(self, wallet):
        msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
              +_("Leave these fields empty if you want to disable encryption.")
        from password_dialog import make_password_dialog, run_password_dialog
        self.set_layout( make_password_dialog(self, wallet, msg) )

        run_password_dialog(self, wallet, self)
Example #24
0
    def choose_wallet_type(self):
        grid = QGridLayout()
        grid.setSpacing(5)

        msg = _("Choose your wallet.")
        label = QLabel(msg)
        label.setWordWrap(True)
        grid.addWidget(label, 0, 0)

        gb = QGroupBox()

        b1 = QRadioButton(gb)
        b1.setText(_("Standard wallet (protected by password)"))
        b1.setChecked(True)

        b2 = QRadioButton(gb)
        b2.setText(_("Multi-signature wallet (two-factor authentication)"))

        grid.addWidget(b1,1,0)
        grid.addWidget(b2,2,0)

        vbox = QVBoxLayout()

        vbox.addLayout(grid)
        vbox.addStretch(1)
        vbox.addLayout(ok_cancel_buttons(self, _('Next')))

        self.set_layout(vbox)
        if not self.exec_():
            return
        
        if b1.isChecked():
            return 'standard'
        elif b2.isChecked():
            return '2of3'
Example #25
0
    def mpk_dialog(self):

        vbox = QVBoxLayout()
        vbox.addWidget(QLabel(_("Please enter your master public key.")))

        grid = QGridLayout()
        grid.setSpacing(8)

        label = QLabel(_("Key")) 
        grid.addWidget(label, 0, 0)
        mpk_e = QTextEdit()
        mpk_e.setMaximumHeight(100)
        grid.addWidget(mpk_e, 0, 1)

        label = QLabel(_("Chain")) 
        #grid.addWidget(label, 1, 0)
        chain_e = QTextEdit()
        chain_e.setMaximumHeight(100)
        #grid.addWidget(chain_e, 1, 1)

        vbox.addLayout(grid)

        vbox.addStretch(1)
        vbox.addLayout(ok_cancel_buttons(self, _('Next')))

        self.set_layout(vbox)
        if not self.exec_(): return None, None

        mpk = str(mpk_e.toPlainText()).strip()
        chain = str(chain_e.toPlainText()).strip()
        return mpk, chain
Example #26
0
    def parse_history(self, items):
        for item in items:
            tx_hash, conf, value, timestamp, balance = item
            time_str = _("unknown")
            if conf > 0:
                try:
                    time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
                except Exception:
                    time_str = _("error")
            if conf == -1:
                time_str = _('unverified')
                icon = "atlas://gui/kivy/theming/light/close"
            elif conf == 0:
                time_str = _('pending')
                icon = "atlas://gui/kivy/theming/light/unconfirmed"
            elif conf < 6:
                conf = max(1, conf)
                icon = "atlas://gui/kivy/theming/light/clock{}".format(conf)
            else:
                icon = "atlas://gui/kivy/theming/light/confirmed"

            label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
            date = timestamp_to_datetime(timestamp)
            rate = run_hook('history_rate', date)
            if self.app.fiat_unit:
                quote_text = "..." if rate is None else "{0:.3} {1}".format(Decimal(value) / 100000000 * Decimal(rate), self.app.fiat_unit)
            else:
                quote_text = ''
            yield (conf, icon, time_str, label, value, tx_hash, quote_text)
Example #27
0
    def update(self):

        self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)]

        requests_list = self.screen.ids.requests_container
        requests_list.clear_widgets()
        for req in self.app.wallet.get_sorted_requests(self.app.electrum_config):
            address = req['address']
            timestamp = req.get('time', 0)
            amount = req.get('amount')
            expiration = req.get('exp', None)
            status = req.get('status')
            signature = req.get('sig')
            ci = Factory.RequestItem()
            ci.address = req['address']
            ci.memo = self.app.wallet.get_label(address)
            status = req.get('status')
            if status == PR_PAID:
                ci.icon = "atlas://gui/kivy/theming/light/confirmed"
            elif status == PR_EXPIRED:
                ci.icon = "atlas://gui/kivy/theming/light/important"
            else:
                ci.icon = "atlas://gui/kivy/theming/light/important"
            ci.amount = self.app.format_amount_and_units(amount) if amount else ''
            ci.date = format_time(timestamp)
            ci.screen = self
            requests_list.add_widget(ci)
Example #28
0
    def before_send(self):
        '''
        Change URL to address before making a send.
        IMPORTANT:
            return False to continue execution of the send
            return True to stop execution of the send
        '''

        if self.win.payto_e.is_multiline():  # only supports single line entries atm
            return False
        if self.win.payto_e.is_pr:
            return
        payto_e = str(self.win.payto_e.toPlainText())
        regex = re.compile(r'^([^\s]+) <([A-Za-z0-9]+)>')  # only do that for converted addresses
        try:
            (url, address) = regex.search(payto_e).groups()
        except AttributeError:
            return False

        if not self.validated:
            msgBox = QMessageBox()
            msgBox.setText(_('WARNING: the address ' + address + ' could not be validated via an additional security check, DNSSEC, and thus may not be correct.'))
            msgBox.setInformativeText(_('Do you wish to continue?'))
            msgBox.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok)
            msgBox.setDefaultButton(QMessageBox.Cancel)
            reply = msgBox.exec_()
            if reply != QMessageBox.Ok:
                return True

        return False
Example #29
0
    def parse_history(self, items):
        for item in items:
            tx_hash, conf, value, timestamp, balance = item
            time_str = _("unknown")
            if conf > 0:
                try:
                    time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
                except Exception:
                    time_str = _("error")
            if conf == -1:
                time_str = _('unverified')
                icon = "atlas://gui/kivy/theming/light/close"
            elif conf == 0:
                time_str = _('pending')
                icon = "atlas://gui/kivy/theming/light/unconfirmed"
            elif conf < 6:
                time_str = ''  # add new to fix error when conf < 0
                conf = max(1, conf)
                icon = "atlas://gui/kivy/theming/light/clock{}".format(conf)
            else:
                icon = "atlas://gui/kivy/theming/light/confirmed"

            if tx_hash:
                label, is_default_label = self.app.wallet.get_label(tx_hash)
            else:
                label = _('Pruned transaction outputs')
                is_default_label = False

            quote_currency = 'USD'
            rate = self.get_history_rate(value, timestamp)
            quote_text = "..." if rate is None else "{0:.3} {1}".format(rate, quote_currency)

            yield (conf, icon, time_str, label, value, tx_hash, quote_text)
Example #30
0
 def toggle_passphrase(self):
     if self.features.passphrase_protection:
         self.msg = _("Confirm on your %s device to disable passphrases")
     else:
         self.msg = _("Confirm on your %s device to enable passphrases")
     enabled = not self.features.passphrase_protection
     self.apply_settings(use_passphrase=enabled)
Example #31
0
class ChannelsList(MyTreeView):
    update_rows = QtCore.pyqtSignal(Abstract_Wallet)
    update_single_row = QtCore.pyqtSignal(Abstract_Wallet, AbstractChannel)
    gossip_db_loaded = QtCore.pyqtSignal()

    class Columns(IntEnum):
        SHORT_CHANID = 0
        NODE_ALIAS = 1
        LOCAL_BALANCE = 2
        REMOTE_BALANCE = 3
        CHANNEL_STATUS = 4

    headers = {
        Columns.SHORT_CHANID: _('Short Channel ID'),
        Columns.NODE_ALIAS: _('Node alias'),
        Columns.LOCAL_BALANCE: _('Local'),
        Columns.REMOTE_BALANCE: _('Remote'),
        Columns.CHANNEL_STATUS: _('Status'),
    }

    filter_columns = [
        Columns.SHORT_CHANID,
        Columns.NODE_ALIAS,
        Columns.CHANNEL_STATUS,
    ]

    _default_item_bg_brush = None  # type: Optional[QBrush]

    def __init__(self, parent):
        super().__init__(parent,
                         self.create_menu,
                         stretch_column=self.Columns.NODE_ALIAS,
                         editable_columns=[])
        self.setModel(QtGui.QStandardItemModel(self))
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.main_window = parent
        self.gossip_db_loaded.connect(self.on_gossip_db)
        self.update_rows.connect(self.do_update_rows)
        self.update_single_row.connect(self.do_update_single_row)
        self.network = self.parent.network
        self.lnworker = self.parent.wallet.lnworker
        self.lnbackups = self.parent.wallet.lnbackups
        self.setSortingEnabled(True)

    def format_fields(self, chan):
        labels = {}
        for subject in (REMOTE, LOCAL):
            bal_minus_htlcs = chan.balance_minus_outgoing_htlcs(
                subject) // 1000
            label = self.parent.format_amount(bal_minus_htlcs)
            other = subject.inverted()
            bal_other = chan.balance(other) // 1000
            bal_minus_htlcs_other = chan.balance_minus_outgoing_htlcs(
                other) // 1000
            if bal_other != bal_minus_htlcs_other:
                label += ' (+' + self.parent.format_amount(
                    bal_other - bal_minus_htlcs_other) + ')'
            labels[subject] = label
        status = chan.get_state_for_GUI()
        closed = chan.is_closed()
        if self.network and self.network.has_channel_db():
            node_info = self.parent.network.channel_db.get_node_info_for_node_id(
                chan.node_id)
            node_alias = (node_info.alias if node_info else '') or ''
        else:
            node_alias = ''
        return [
            chan.short_id_for_GUI(), node_alias,
            '' if closed else labels[LOCAL], '' if closed else labels[REMOTE],
            status
        ]

    def on_success(self, txid):
        self.main_window.show_error('Channel closed' + '\n' + txid)

    def on_failure(self, exc_info):
        type_, e, tb = exc_info
        traceback.print_tb(tb)
        self.main_window.show_error('Failed to close channel:\n{}'.format(
            repr(e)))

    def close_channel(self, channel_id):
        msg = _('Close channel?')
        if not self.parent.question(msg):
            return

        def task():
            coro = self.lnworker.close_channel(channel_id)
            return self.network.run_from_another_thread(coro)

        WaitingDialog(self, 'please wait..', task, self.on_success,
                      self.on_failure)

    def force_close(self, channel_id):
        chan = self.lnworker.channels[channel_id]
        to_self_delay = chan.config[REMOTE].to_self_delay
        msg = _('Force-close channel?') + '\n\n'\
              + _('Funds retrieved from this channel will not be available before {} blocks after forced closure.').format(to_self_delay) + ' '\
              + _('After that delay, funds will be sent to an address derived from your wallet seed.') + '\n\n'\
              + _('In the meantime, channel funds will not be recoverable from your seed, and might be lost if you lose your wallet.') + ' '\
              + _('To prevent that, you should have a backup of this channel on another device.')
        if self.parent.question(msg):

            def task():
                coro = self.lnworker.force_close_channel(channel_id)
                return self.network.run_from_another_thread(coro)

            WaitingDialog(self, 'please wait..', task, self.on_success,
                          self.on_failure)

    def remove_channel(self, channel_id):
        if self.main_window.question(
                _('Are you sure you want to delete this channel? This will purge associated transactions from your wallet history.'
                  )):
            self.lnworker.remove_channel(channel_id)

    def remove_channel_backup(self, channel_id):
        if self.main_window.question(_('Remove channel backup?')):
            self.lnbackups.remove_channel_backup(channel_id)

    def export_channel_backup(self, channel_id):
        msg = ' '.join([
            _("Channel backups can be imported in another instance of the same wallet, by scanning this QR code."
              ),
            _("Please note that channel backups cannot be used to restore your channels."
              ),
            _("If you lose your wallet file, the only thing you can do with a backup is to request your channel to be closed, so that your funds will be sent on-chain."
              ),
        ])
        data = self.lnworker.export_channel_backup(channel_id)
        self.main_window.show_qrcode(data,
                                     'channel backup',
                                     help_text=msg,
                                     show_copy_text_btn=True)

    def request_force_close(self, channel_id):
        def task():
            coro = self.lnbackups.request_force_close(channel_id)
            return self.network.run_from_another_thread(coro)

        def on_success(b):
            self.main_window.show_message('success')

        WaitingDialog(self, 'please wait..', task, on_success, self.on_failure)

    def create_menu(self, position):
        menu = QMenu()
        menu.setSeparatorsCollapsible(
            True)  # consecutive separators are merged together
        selected = self.selected_in_column(self.Columns.NODE_ALIAS)
        if not selected:
            menu.addAction(
                _("Import channel backup"),
                lambda: self.parent.do_process_from_text_channel_backup())
            menu.exec_(self.viewport().mapToGlobal(position))
            return
        multi_select = len(selected) > 1
        if multi_select:
            return
        idx = self.indexAt(position)
        if not idx.isValid():
            return
        item = self.model().itemFromIndex(idx)
        if not item:
            return
        channel_id = idx.sibling(idx.row(),
                                 self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
        if channel_id in self.lnbackups.channel_backups:
            menu.addAction(_("Request force-close"),
                           lambda: self.request_force_close(channel_id))
            menu.addAction(_("Delete"),
                           lambda: self.remove_channel_backup(channel_id))
            menu.exec_(self.viewport().mapToGlobal(position))
            return
        chan = self.lnworker.channels[channel_id]
        menu.addAction(_("Details..."),
                       lambda: self.parent.show_channel(channel_id))
        cc = self.add_copy_menu(menu, idx)
        cc.addAction(
            _("Node ID"),
            lambda: self.place_text_on_clipboard(chan.node_id.hex(),
                                                 title=_("Node ID")))
        cc.addAction(
            _("Long Channel ID"),
            lambda: self.place_text_on_clipboard(channel_id.hex(),
                                                 title=_("Long Channel ID")))
        if not chan.is_closed():
            if not chan.is_frozen_for_sending():
                menu.addAction(_("Freeze (for sending)"),
                               lambda: chan.set_frozen_for_sending(True))
            else:
                menu.addAction(_("Unfreeze (for sending)"),
                               lambda: chan.set_frozen_for_sending(False))
            if not chan.is_frozen_for_receiving():
                menu.addAction(_("Freeze (for receiving)"),
                               lambda: chan.set_frozen_for_receiving(True))
            else:
                menu.addAction(_("Unfreeze (for receiving)"),
                               lambda: chan.set_frozen_for_receiving(False))

        funding_tx = self.parent.wallet.db.get_transaction(
            chan.funding_outpoint.txid)
        if funding_tx:
            menu.addAction(_("View funding transaction"),
                           lambda: self.parent.show_transaction(funding_tx))
        if not chan.is_closed():
            menu.addSeparator()
            if chan.peer_state == PeerState.GOOD:
                menu.addAction(_("Close channel"),
                               lambda: self.close_channel(channel_id))
            menu.addAction(_("Force-close channel"),
                           lambda: self.force_close(channel_id))
        else:
            item = chan.get_closing_height()
            if item:
                txid, height, timestamp = item
                closing_tx = self.lnworker.lnwatcher.db.get_transaction(txid)
                if closing_tx:
                    menu.addAction(
                        _("View closing transaction"),
                        lambda: self.parent.show_transaction(closing_tx))
        menu.addSeparator()
        menu.addAction(_("Export backup"),
                       lambda: self.export_channel_backup(channel_id))
        if chan.is_redeemed():
            menu.addSeparator()
            menu.addAction(_("Delete"),
                           lambda: self.remove_channel(channel_id))
        menu.exec_(self.viewport().mapToGlobal(position))

    @QtCore.pyqtSlot(Abstract_Wallet, AbstractChannel)
    def do_update_single_row(self, wallet: Abstract_Wallet,
                             chan: AbstractChannel):
        if wallet != self.parent.wallet:
            return
        for row in range(self.model().rowCount()):
            item = self.model().item(row, self.Columns.NODE_ALIAS)
            if item.data(ROLE_CHANNEL_ID) != chan.channel_id:
                continue
            for column, v in enumerate(self.format_fields(chan)):
                self.model().item(row, column).setData(v,
                                                       QtCore.Qt.DisplayRole)
            items = [self.model().item(row, column) for column in self.Columns]
            self._update_chan_frozen_bg(chan=chan, items=items)
        if wallet.lnworker:
            self.update_can_send(wallet.lnworker)

    @QtCore.pyqtSlot()
    def on_gossip_db(self):
        self.do_update_rows(self.parent.wallet)

    @QtCore.pyqtSlot(Abstract_Wallet)
    def do_update_rows(self, wallet):
        if wallet != self.parent.wallet:
            return
        channels = list(
            wallet.lnworker.channels.values()) if wallet.lnworker else []
        backups = list(wallet.lnbackups.channel_backups.values())
        if wallet.lnworker:
            self.update_can_send(wallet.lnworker)
        self.model().clear()
        self.update_headers(self.headers)
        for chan in channels + backups:
            items = [QtGui.QStandardItem(x) for x in self.format_fields(chan)]
            self.set_editability(items)
            if self._default_item_bg_brush is None:
                self._default_item_bg_brush = items[
                    self.Columns.NODE_ALIAS].background()
            items[self.Columns.NODE_ALIAS].setData(chan.channel_id,
                                                   ROLE_CHANNEL_ID)
            items[self.Columns.NODE_ALIAS].setFont(QFont(MONOSPACE_FONT))
            items[self.Columns.LOCAL_BALANCE].setFont(QFont(MONOSPACE_FONT))
            items[self.Columns.REMOTE_BALANCE].setFont(QFont(MONOSPACE_FONT))
            self._update_chan_frozen_bg(chan=chan, items=items)
            self.model().insertRow(0, items)

        self.sortByColumn(self.Columns.SHORT_CHANID, Qt.DescendingOrder)

    def _update_chan_frozen_bg(self, *, chan: AbstractChannel,
                               items: Sequence[QStandardItem]):
        assert self._default_item_bg_brush is not None
        # frozen for sending
        item = items[self.Columns.LOCAL_BALANCE]
        if chan.is_frozen_for_sending():
            item.setBackground(ColorScheme.BLUE.as_color(True))
            item.setToolTip(
                _("This channel is frozen for sending. It will not be used for outgoing payments."
                  ))
        else:
            item.setBackground(self._default_item_bg_brush)
            item.setToolTip("")
        # frozen for receiving
        item = items[self.Columns.REMOTE_BALANCE]
        if chan.is_frozen_for_receiving():
            item.setBackground(ColorScheme.BLUE.as_color(True))
            item.setToolTip(
                _("This channel is frozen for receiving. It will not be included in invoices."
                  ))
        else:
            item.setBackground(self._default_item_bg_brush)
            item.setToolTip("")

    def update_can_send(self, lnworker: LNWallet):
        msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\
              + ' ' + self.parent.base_unit() + '; '\
              + _('can receive') + ' ' + self.parent.format_amount(lnworker.num_sats_can_receive())\
              + ' ' + self.parent.base_unit()
        self.can_send_label.setText(msg)

    def get_toolbar(self):
        h = QHBoxLayout()
        self.can_send_label = QLabel('')
        h.addWidget(self.can_send_label)
        h.addStretch()
        self.swap_button = EnterButton(_('Swap'), self.swap_dialog)
        self.swap_button.setEnabled(self.parent.wallet.has_lightning())
        self.new_channel_button = EnterButton(_('Open Channel'),
                                              self.new_channel_with_warning)
        self.new_channel_button.setEnabled(self.parent.wallet.has_lightning())
        h.addWidget(self.new_channel_button)
        h.addWidget(self.swap_button)
        return h

    def new_channel_with_warning(self):
        if not self.parent.wallet.lnworker.channels:
            warning1 = _("Lightning support in Electrum is experimental. "
                         "Do not put large amounts in lightning channels.")
            warning2 = _(
                "Funds stored in lightning channels are not recoverable from your seed. "
                "You must backup your wallet file everytime you create a new channel."
            )
            answer = self.parent.question(
                _('Do you want to create your first channel?') + '\n\n' +
                _('WARNINGS') + ': ' + '\n\n' + warning1 + '\n\n' + warning2)
            if answer:
                self.new_channel_dialog()
        else:
            self.new_channel_dialog()

    def statistics_dialog(self):
        channel_db = self.parent.network.channel_db
        capacity = self.parent.format_amount(
            channel_db.capacity()) + ' ' + self.parent.base_unit()
        d = WindowModalDialog(self.parent, _('Lightning Network Statistics'))
        d.setMinimumWidth(400)
        vbox = QVBoxLayout(d)
        h = QGridLayout()
        h.addWidget(QLabel(_('Nodes') + ':'), 0, 0)
        h.addWidget(QLabel('{}'.format(channel_db.num_nodes)), 0, 1)
        h.addWidget(QLabel(_('Channels') + ':'), 1, 0)
        h.addWidget(QLabel('{}'.format(channel_db.num_channels)), 1, 1)
        h.addWidget(QLabel(_('Capacity') + ':'), 2, 0)
        h.addWidget(QLabel(capacity), 2, 1)
        vbox.addLayout(h)
        vbox.addLayout(Buttons(OkButton(d)))
        d.exec_()

    def new_channel_dialog(self):
        lnworker = self.parent.wallet.lnworker
        d = WindowModalDialog(self.parent, _('Open Channel'))
        vbox = QVBoxLayout(d)
        vbox.addWidget(
            QLabel(_('Enter Remote Node ID or connection string or invoice')))
        local_nodeid = FreezableLineEdit()
        local_nodeid.setMinimumWidth(700)
        local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey))
        local_nodeid.setFrozen(True)
        local_nodeid.setCursorPosition(0)
        remote_nodeid = QLineEdit()
        remote_nodeid.setMinimumWidth(700)
        amount_e = BTCAmountEdit(self.parent.get_decimal_point)

        # max button
        def spend_max():
            amount_e.setFrozen(max_button.isChecked())
            if not max_button.isChecked():
                return
            make_tx = self.parent.mktx_for_open_channel('!')
            try:
                tx = make_tx(None)
            except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
                max_button.setChecked(False)
                amount_e.setFrozen(False)
                self.main_window.show_error(str(e))
                return
            amount = tx.output_value()
            amount = min(amount, LN_MAX_FUNDING_SAT)
            amount_e.setAmount(amount)

        max_button = EnterButton(_("Max"), spend_max)
        max_button.setFixedWidth(100)
        max_button.setCheckable(True)
        clear_button = QPushButton(d, text=_('Clear'))
        clear_button.setFixedWidth(100)

        def on_clear():
            amount_e.setText('')
            amount_e.setFrozen(False)
            amount_e.repaint()  # macOS hack for #6269
            remote_nodeid.setText('')
            remote_nodeid.repaint()  # macOS hack for #6269
            max_button.setChecked(False)
            max_button.repaint()  # macOS hack for #6269

        clear_button.clicked.connect(on_clear)

        h = QGridLayout()
        h.addWidget(QLabel(_('Your Node ID')), 0, 0)
        h.addWidget(local_nodeid, 0, 1, 1, 4)
        h.addWidget(QLabel(_('Remote Node ID')), 1, 0)
        h.addWidget(remote_nodeid, 1, 1, 1, 4)
        h.addWidget(QLabel('Amount'), 3, 0)
        h.addWidget(amount_e, 3, 1)
        h.addWidget(max_button, 3, 2)
        h.addWidget(clear_button, 3, 3)
        vbox.addLayout(h)
        ok_button = OkButton(d)
        ok_button.setDefault(True)
        vbox.addLayout(Buttons(CancelButton(d), ok_button))
        if not d.exec_():
            return
        if max_button.isChecked(
        ) and amount_e.get_amount() < LN_MAX_FUNDING_SAT:
            # if 'max' enabled and amount is strictly less than max allowed,
            # that means we have fewer coins than max allowed, and hence we can
            # spend all coins
            funding_sat = '!'
        else:
            funding_sat = amount_e.get_amount()
        connect_str = str(remote_nodeid.text()).strip()
        if not connect_str or not funding_sat:
            return
        self.parent.open_channel(connect_str, funding_sat, 0)

    def swap_dialog(self):
        from .swap_dialog import SwapDialog
        d = SwapDialog(self.parent)
        d.run()
Example #32
0
 def onclick(self):
     custom_message_box(icon=QMessageBox.Information,
                        parent=self,
                        title=_('Info'),
                        text=self.help_text,
                        rich_text=True)
Example #33
0
 def mouseReleaseEvent(self, x):
     custom_message_box(icon=QMessageBox.Information,
                        parent=self,
                        title=_('Help'),
                        text=self.help_text)
Example #34
0
 def done_processing(self):
     QMessageBox.information(None, _("Labels synchronised"),
                             _("Your labels have been synchronised."))
Example #35
0
 def settings_widget(self, window):
     return EnterButton(_('Settings'), partial(self.settings_dialog,
                                               window))
Example #36
0
    def new_channel_dialog(self):
        lnworker = self.parent.wallet.lnworker
        d = WindowModalDialog(self.parent, _('Open Channel'))
        vbox = QVBoxLayout(d)
        vbox.addWidget(
            QLabel(_('Enter Remote Node ID or connection string or invoice')))
        local_nodeid = FreezableLineEdit()
        local_nodeid.setMinimumWidth(700)
        local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey))
        local_nodeid.setFrozen(True)
        local_nodeid.setCursorPosition(0)
        remote_nodeid = QLineEdit()
        remote_nodeid.setMinimumWidth(700)
        amount_e = BTCAmountEdit(self.parent.get_decimal_point)

        # max button
        def spend_max():
            amount_e.setFrozen(max_button.isChecked())
            if not max_button.isChecked():
                return
            make_tx = self.parent.mktx_for_open_channel('!')
            try:
                tx = make_tx(None)
            except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
                max_button.setChecked(False)
                amount_e.setFrozen(False)
                self.main_window.show_error(str(e))
                return
            amount = tx.output_value()
            amount = min(amount, LN_MAX_FUNDING_SAT)
            amount_e.setAmount(amount)

        max_button = EnterButton(_("Max"), spend_max)
        max_button.setFixedWidth(100)
        max_button.setCheckable(True)
        clear_button = QPushButton(d, text=_('Clear'))
        clear_button.setFixedWidth(100)

        def on_clear():
            amount_e.setText('')
            amount_e.setFrozen(False)
            amount_e.repaint()  # macOS hack for #6269
            remote_nodeid.setText('')
            remote_nodeid.repaint()  # macOS hack for #6269
            max_button.setChecked(False)
            max_button.repaint()  # macOS hack for #6269

        clear_button.clicked.connect(on_clear)

        h = QGridLayout()
        h.addWidget(QLabel(_('Your Node ID')), 0, 0)
        h.addWidget(local_nodeid, 0, 1, 1, 4)
        h.addWidget(QLabel(_('Remote Node ID')), 1, 0)
        h.addWidget(remote_nodeid, 1, 1, 1, 4)
        h.addWidget(QLabel('Amount'), 3, 0)
        h.addWidget(amount_e, 3, 1)
        h.addWidget(max_button, 3, 2)
        h.addWidget(clear_button, 3, 3)
        vbox.addLayout(h)
        ok_button = OkButton(d)
        ok_button.setDefault(True)
        vbox.addLayout(Buttons(CancelButton(d), ok_button))
        if not d.exec_():
            return
        if max_button.isChecked(
        ) and amount_e.get_amount() < LN_MAX_FUNDING_SAT:
            # if 'max' enabled and amount is strictly less than max allowed,
            # that means we have fewer coins than max allowed, and hence we can
            # spend all coins
            funding_sat = '!'
        else:
            funding_sat = amount_e.get_amount()
        connect_str = str(remote_nodeid.text()).strip()
        if not connect_str or not funding_sat:
            return
        self.parent.open_channel(connect_str, funding_sat, 0)
Example #37
0
 def update_can_send(self, lnworker: LNWallet):
     msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\
           + ' ' + self.parent.base_unit() + '; '\
           + _('can receive') + ' ' + self.parent.format_amount(lnworker.num_sats_can_receive())\
           + ' ' + self.parent.base_unit()
     self.can_send_label.setText(msg)
Example #38
0
 def remove_channel_backup(self, channel_id):
     if self.main_window.question(_('Remove channel backup?')):
         self.lnbackups.remove_channel_backup(channel_id)
Example #39
0
 def __init__(self, parent):
     QTreeWidget.__init__(self)
     self.parent = parent
     self.setHeaderLabels([_('Connected node'), _('Height')])
     self.setContextMenuPolicy(Qt.CustomContextMenu)
     self.customContextMenuRequested.connect(self.create_menu)
Example #40
0
    def create_menu(self, position):
        menu = QMenu()
        menu.setSeparatorsCollapsible(
            True)  # consecutive separators are merged together
        selected = self.selected_in_column(self.Columns.NODE_ALIAS)
        if not selected:
            menu.addAction(
                _("Import channel backup"),
                lambda: self.parent.do_process_from_text_channel_backup())
            menu.exec_(self.viewport().mapToGlobal(position))
            return
        multi_select = len(selected) > 1
        if multi_select:
            return
        idx = self.indexAt(position)
        if not idx.isValid():
            return
        item = self.model().itemFromIndex(idx)
        if not item:
            return
        channel_id = idx.sibling(idx.row(),
                                 self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
        if channel_id in self.lnbackups.channel_backups:
            menu.addAction(_("Request force-close"),
                           lambda: self.request_force_close(channel_id))
            menu.addAction(_("Delete"),
                           lambda: self.remove_channel_backup(channel_id))
            menu.exec_(self.viewport().mapToGlobal(position))
            return
        chan = self.lnworker.channels[channel_id]
        menu.addAction(_("Details..."),
                       lambda: self.parent.show_channel(channel_id))
        cc = self.add_copy_menu(menu, idx)
        cc.addAction(
            _("Node ID"),
            lambda: self.place_text_on_clipboard(chan.node_id.hex(),
                                                 title=_("Node ID")))
        cc.addAction(
            _("Long Channel ID"),
            lambda: self.place_text_on_clipboard(channel_id.hex(),
                                                 title=_("Long Channel ID")))
        if not chan.is_closed():
            if not chan.is_frozen_for_sending():
                menu.addAction(_("Freeze (for sending)"),
                               lambda: chan.set_frozen_for_sending(True))
            else:
                menu.addAction(_("Unfreeze (for sending)"),
                               lambda: chan.set_frozen_for_sending(False))
            if not chan.is_frozen_for_receiving():
                menu.addAction(_("Freeze (for receiving)"),
                               lambda: chan.set_frozen_for_receiving(True))
            else:
                menu.addAction(_("Unfreeze (for receiving)"),
                               lambda: chan.set_frozen_for_receiving(False))

        funding_tx = self.parent.wallet.db.get_transaction(
            chan.funding_outpoint.txid)
        if funding_tx:
            menu.addAction(_("View funding transaction"),
                           lambda: self.parent.show_transaction(funding_tx))
        if not chan.is_closed():
            menu.addSeparator()
            if chan.peer_state == PeerState.GOOD:
                menu.addAction(_("Close channel"),
                               lambda: self.close_channel(channel_id))
            menu.addAction(_("Force-close channel"),
                           lambda: self.force_close(channel_id))
        else:
            item = chan.get_closing_height()
            if item:
                txid, height, timestamp = item
                closing_tx = self.lnworker.lnwatcher.db.get_transaction(txid)
                if closing_tx:
                    menu.addAction(
                        _("View closing transaction"),
                        lambda: self.parent.show_transaction(closing_tx))
        menu.addSeparator()
        menu.addAction(_("Export backup"),
                       lambda: self.export_channel_backup(channel_id))
        if chan.is_redeemed():
            menu.addSeparator()
            menu.addAction(_("Delete"),
                           lambda: self.remove_channel(channel_id))
        menu.exec_(self.viewport().mapToGlobal(position))
Example #41
0
 def exchange_rate_button(self, grid):
     quote_currency = self.config.get("currency", "EUR")
     self.fiat_button = EnterButton(_(quote_currency), self.fiat_dialog)
     grid.addWidget(self.fiat_button, 4, 3, Qt.AlignHCenter)
Example #42
0
 def remove_channel(self, channel_id):
     if self.main_window.question(
             _('Are you sure you want to delete this channel? This will purge associated transactions from your wallet history.'
               )):
         self.lnworker.remove_channel(channel_id)
Example #43
0
    def history_tab_update(self):
        if self.config.get('history_rates', 'unchecked') == "checked":
            cur_exchange = self.config.get('use_exchange', "Blockchain")
            try:
                tx_list = self.tx_list
            except Exception:
                return

            try:
                mintimestr = datetime.datetime.fromtimestamp(
                    int(
                        min(tx_list.items(), key=lambda x: x[1]['timestamp'])
                        [1]['timestamp'])).strftime('%Y-%m-%d')
            except Exception:
                return
            maxtimestr = datetime.datetime.now().strftime('%Y-%m-%d')

            if cur_exchange == "CoinDesk":
                try:
                    resp_hist = self.exchanger.get_json(
                        'api.coindesk.com',
                        "/v1/bpi/historical/close.json?start=" + mintimestr +
                        "&end=" + maxtimestr)
                except Exception:
                    return
            elif cur_exchange == "Winkdex":
                try:
                    resp_hist = self.exchanger.get_json(
                        'winkdex.com',
                        "/static/data/0_86400_730.json")['prices']
                except Exception:
                    return
            elif cur_exchange == "BitcoinVenezuela":
                cur_currency = self.config.get('currency', "EUR")
                if cur_currency == "VEF":
                    try:
                        resp_hist = self.exchanger.get_json(
                            'api.bitcoinvenezuela.com',
                            "/historical/index.php?coin=BTC")['VEF_BTC']
                    except Exception:
                        return
                elif cur_currency == "ARS":
                    try:
                        resp_hist = self.exchanger.get_json(
                            'api.bitcoinvenezuela.com',
                            "/historical/index.php?coin=BTC")['ARS_BTC']
                    except Exception:
                        return
                else:
                    return

            self.gui.main_window.is_edit = True
            self.gui.main_window.history_list.setColumnCount(6)
            self.gui.main_window.history_list.setHeaderLabels([
                '',
                _('Date'),
                _('Description'),
                _('Amount'),
                _('Balance'),
                _('Fiat Amount')
            ])
            root = self.gui.main_window.history_list.invisibleRootItem()
            childcount = root.childCount()
            for i in range(childcount):
                item = root.child(i)
                try:
                    tx_info = tx_list[str(
                        item.data(0, Qt.UserRole).toPyObject())]
                except Exception:
                    newtx = self.wallet.get_tx_history()
                    v = newtx[[x[0] for x in newtx].index(
                        str(item.data(0, Qt.UserRole).toPyObject()))][3]

                    tx_info = {'timestamp': int(time.time()), 'value': v}
                    pass
                tx_time = int(tx_info['timestamp'])
                if cur_exchange == "CoinDesk":
                    tx_time_str = datetime.datetime.fromtimestamp(
                        tx_time).strftime('%Y-%m-%d')
                    try:
                        tx_USD_val = "%.2f %s" % (
                            Decimal(str(tx_info['value'])) / 100000000 *
                            Decimal(resp_hist['bpi'][tx_time_str]), "USD")
                    except KeyError:
                        tx_USD_val = "%.2f %s" % (self.btc_rate * Decimal(
                            str(tx_info['value'])) / 100000000, "USD")
                elif cur_exchange == "Winkdex":
                    tx_time_str = int(tx_time) - (int(tx_time) %
                                                  (60 * 60 * 24))
                    try:
                        tx_rate = resp_hist[[x['x'] for x in resp_hist
                                             ].index(tx_time_str)]['y']
                        tx_USD_val = "%.2f %s" % (Decimal(tx_info['value']) /
                                                  100000000 * Decimal(tx_rate),
                                                  "USD")
                    except ValueError:
                        tx_USD_val = "%.2f %s" % (self.btc_rate * Decimal(
                            tx_info['value']) / 100000000, "USD")
                elif cur_exchange == "BitcoinVenezuela":
                    tx_time_str = datetime.datetime.fromtimestamp(
                        tx_time).strftime('%Y-%m-%d')
                    try:
                        num = resp_hist[tx_time_str].replace(',', '')
                        tx_BTCVEN_val = "%.2f %s" % (
                            Decimal(str(tx_info['value'])) / 100000000 *
                            Decimal(num), cur_currency)
                    except KeyError:
                        tx_BTCVEN_val = _("No data")

                if cur_exchange == "CoinDesk" or cur_exchange == "Winkdex":
                    item.setText(5, tx_USD_val)
                elif cur_exchange == "BitcoinVenezuela":
                    item.setText(5, tx_BTCVEN_val)
                if Decimal(str(tx_info['value'])) < 0:
                    item.setForeground(5, QBrush(QColor("#BC1E1E")))

            for i, width in enumerate(
                    self.gui.main_window.column_widths['history']):
                self.gui.main_window.history_list.setColumnWidth(i, width)
            self.gui.main_window.history_list.setColumnWidth(4, 140)
            self.gui.main_window.history_list.setColumnWidth(5, 120)
            self.gui.main_window.is_edit = False
Example #44
0
    def __init__(self, network, config, wizard=False):
        self.network = network
        self.config = config
        self.protocol = None
        self.tor_proxy = None

        self.tabs = tabs = QTabWidget()
        server_tab = QWidget()
        proxy_tab = QWidget()
        blockchain_tab = QWidget()
        tabs.addTab(blockchain_tab, _('Overview'))
        tabs.addTab(server_tab, _('Server'))
        tabs.addTab(proxy_tab, _('Proxy'))

        # server tab
        grid = QGridLayout(server_tab)
        grid.setSpacing(8)

        self.server_host = QLineEdit()
        self.server_host.setFixedWidth(200)
        self.server_port = QLineEdit()
        self.server_port.setFixedWidth(60)
        self.autoconnect_cb = QCheckBox(_('Select server automatically'))
        self.autoconnect_cb.setEnabled(
            self.config.is_modifiable('auto_connect'))

        self.server_host.editingFinished.connect(self.set_server)
        self.server_port.editingFinished.connect(self.set_server)
        self.autoconnect_cb.clicked.connect(self.set_server)
        self.autoconnect_cb.clicked.connect(self.update)

        msg = ' '.join([
            _("If auto-connect is enabled, Electrum will always use a server that is on the longest blockchain."
              ),
            _("If it is disabled, you have to choose a server you want to use. Electrum will warn you if your server is lagging."
              )
        ])
        grid.addWidget(self.autoconnect_cb, 0, 0, 1, 3)
        grid.addWidget(HelpButton(msg), 0, 4)

        grid.addWidget(QLabel(_('Server') + ':'), 1, 0)
        grid.addWidget(self.server_host, 1, 1, 1, 2)
        grid.addWidget(self.server_port, 1, 3)

        label = _('Server peers') if network.is_connected() else _(
            'Default Servers')
        grid.addWidget(QLabel(label), 2, 0, 1, 5)
        self.servers_list = ServerListWidget(self)
        grid.addWidget(self.servers_list, 3, 0, 1, 5)

        # Proxy tab
        grid = QGridLayout(proxy_tab)
        grid.setSpacing(8)

        # proxy setting
        self.proxy_cb = QCheckBox(_('Use proxy'))
        self.proxy_cb.clicked.connect(self.check_disable_proxy)
        self.proxy_cb.clicked.connect(self.set_proxy)

        self.proxy_mode = QComboBox()
        self.proxy_mode.addItems(['SOCKS4', 'SOCKS5', 'HTTP'])
        self.proxy_host = QLineEdit()
        self.proxy_host.setFixedWidth(200)
        self.proxy_port = QLineEdit()
        self.proxy_port.setFixedWidth(60)
        self.proxy_user = QLineEdit()
        self.proxy_user.setPlaceholderText(_("Proxy user"))
        self.proxy_password = QLineEdit()
        self.proxy_password.setPlaceholderText(_("Password"))
        self.proxy_password.setEchoMode(QLineEdit.Password)
        self.proxy_password.setFixedWidth(60)

        self.proxy_mode.currentIndexChanged.connect(self.set_proxy)
        self.proxy_host.editingFinished.connect(self.set_proxy)
        self.proxy_port.editingFinished.connect(self.set_proxy)
        self.proxy_user.editingFinished.connect(self.set_proxy)
        self.proxy_password.editingFinished.connect(self.set_proxy)

        self.proxy_mode.currentIndexChanged.connect(
            self.proxy_settings_changed)
        self.proxy_host.textEdited.connect(self.proxy_settings_changed)
        self.proxy_port.textEdited.connect(self.proxy_settings_changed)
        self.proxy_user.textEdited.connect(self.proxy_settings_changed)
        self.proxy_password.textEdited.connect(self.proxy_settings_changed)

        self.tor_cb = QCheckBox(_("Use Tor Proxy"))
        self.tor_cb.setIcon(QIcon(":icons/tor_logo.png"))
        self.tor_cb.hide()
        self.tor_cb.clicked.connect(self.use_tor_proxy)

        grid.addWidget(self.tor_cb, 1, 0, 1, 3)
        grid.addWidget(self.proxy_cb, 2, 0, 1, 3)
        grid.addWidget(
            HelpButton(
                _('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.'
                  )), 2, 4)
        grid.addWidget(self.proxy_mode, 4, 1)
        grid.addWidget(self.proxy_host, 4, 2)
        grid.addWidget(self.proxy_port, 4, 3)
        grid.addWidget(self.proxy_user, 5, 2)
        grid.addWidget(self.proxy_password, 5, 3)
        grid.setRowStretch(7, 1)

        # Blockchain Tab
        grid = QGridLayout(blockchain_tab)
        msg = ' '.join([
            _("SnowGem Electrum connects to several nodes in order to download block headers and find out the longest blockchain."
              ),
            _("This blockchain is used to verify the transactions sent by your transaction server."
              )
        ])
        self.status_label = QLabel('')
        grid.addWidget(QLabel(_('Status') + ':'), 0, 0)
        grid.addWidget(self.status_label, 0, 1, 1, 3)
        grid.addWidget(HelpButton(msg), 0, 4)

        self.server_label = QLabel('')
        msg = _(
            "SnowGem Electrum sends your wallet addresses to a single server, in order to receive your transaction history."
        )
        grid.addWidget(QLabel(_('Server') + ':'), 1, 0)
        grid.addWidget(self.server_label, 1, 1, 1, 3)
        grid.addWidget(HelpButton(msg), 1, 4)

        self.height_label = QLabel('')
        msg = _('This is the height of your local copy of the blockchain.')
        grid.addWidget(QLabel(_('Blockchain') + ':'), 2, 0)
        grid.addWidget(self.height_label, 2, 1)
        grid.addWidget(HelpButton(msg), 2, 4)

        self.split_label = QLabel('')
        grid.addWidget(self.split_label, 3, 0, 1, 3)

        self.nodes_list_widget = NodesListWidget(self)
        grid.addWidget(self.nodes_list_widget, 5, 0, 1, 5)

        vbox = QVBoxLayout()
        vbox.addWidget(tabs)
        self.layout_ = vbox
        # tor detector
        self.td = td = TorDetector()
        td.found_proxy.connect(self.suggest_proxy)
        td.start()

        self.fill_in_proxy_settings()
        self.update()
Example #45
0
    def sign_transaction(self, tx, password):
        if tx.is_complete():
            return
        client = self.get_client()
        self.signing = True
        inputs = []
        inputsPaths = []
        pubKeys = []
        chipInputs = []
        redeemScripts = []
        signatures = []
        preparedTrustedInputs = []
        changePath = ""
        changeAmount = None
        output = None
        outputAmount = None
        p2shTransaction = False
        segwitTransaction = False
        pin = ""
        self.get_client() # prompt for the PIN before displaying the dialog if necessary

        # Fetch inputs of the transaction to sign
        derivations = self.get_tx_derivations(tx)
        for txin in tx.inputs():
            if txin['type'] == 'coinbase':
                self.give_error("Coinbase not supported")     # should never happen

            if txin['type'] in ['p2sh']:
                p2shTransaction = True

            if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
                if not self.get_client_electrum().supports_segwit():
                    self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
                segwitTransaction = True

            if txin['type'] in ['p2wpkh', 'p2wsh']:
                if not self.get_client_electrum().supports_native_segwit():
                    self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
                segwitTransaction = True

            pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
            for i, x_pubkey in enumerate(x_pubkeys):
                if x_pubkey in derivations:
                    signingPos = i
                    s = derivations.get(x_pubkey)
                    hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1])
                    break
            else:
                self.give_error("No matching x_key for sign_transaction") # should never happen

            redeemScript = Transaction.get_preimage_script(txin)
            inputs.append([txin['prev_tx'].raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos, txin.get('sequence', 0xffffffff - 1) ])
            inputsPaths.append(hwAddress)
            pubKeys.append(pubkeys)

        # Sanity check
        if p2shTransaction:
            for txin in tx.inputs():
                if txin['type'] != 'p2sh':
                    self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen

        txOutput = var_int(len(tx.outputs()))
        for txout in tx.outputs():
            output_type, addr, amount = txout
            txOutput += int_to_hex(amount, 8)
            script = tx.pay_script(output_type, addr)
            txOutput += var_int(len(script)//2)
            txOutput += script
        txOutput = bfh(txOutput)

        # Recognize outputs - only one output and one change is authorized
        if not p2shTransaction:
            if not self.get_client_electrum().supports_multi_output():
                if len(tx.outputs()) > 2:
                    self.give_error("Transaction with more than 2 outputs not supported")
            for _type, address, amount in tx.outputs():
                assert _type == TYPE_ADDRESS
                info = tx.output_info.get(address)
                if (info is not None) and (len(tx.outputs()) != 1):
                    index, xpubs, m = info
                    changePath = self.get_derivation()[2:] + "/%d/%d"%index
                    changeAmount = amount
                else:
                    output = address
                    outputAmount = amount

        self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
        try:
            # Get trusted inputs from the original transactions
            for utxo in inputs:
                sequence = int_to_hex(utxo[5], 4)
                if segwitTransaction:
                    txtmp = bitcoinTransaction(bfh(utxo[0]))
                    tmp = bfh(utxo[3])[::-1]
                    tmp += bfh(int_to_hex(utxo[1], 4))
                    tmp += txtmp.outputs[utxo[1]].amount
                    chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence})
                    redeemScripts.append(bfh(utxo[2]))
                elif not p2shTransaction:
                    txtmp = bitcoinTransaction(bfh(utxo[0]))
                    trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1])
                    trustedInput['sequence'] = sequence
                    chipInputs.append(trustedInput)
                    redeemScripts.append(txtmp.outputs[utxo[1]].script)
                else:
                    tmp = bfh(utxo[3])[::-1]
                    tmp += bfh(int_to_hex(utxo[1], 4))
                    chipInputs.append({'value' : tmp, 'sequence' : sequence})
                    redeemScripts.append(bfh(utxo[2]))

            # Sign all inputs
            firstTransaction = True
            inputIndex = 0
            rawTx = tx.serialize()
            self.get_client().enableAlternate2fa(False)
            if segwitTransaction:
                self.get_client().startUntrustedTransaction(True, inputIndex,
                                                            chipInputs, redeemScripts[inputIndex])
                outputData = self.get_client().finalizeInputFull(txOutput)
                outputData['outputData'] = txOutput
                transactionOutput = outputData['outputData']
                if outputData['confirmationNeeded']:
                    outputData['address'] = output
                    self.handler.finished()
                    pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin
                    if not pin:
                        raise UserWarning()
                    if pin != 'paired':
                        self.handler.show_message(_("Confirmed. Signing Transaction..."))
                while inputIndex < len(inputs):
                    singleInput = [ chipInputs[inputIndex] ]
                    self.get_client().startUntrustedTransaction(False, 0,
                                                            singleInput, redeemScripts[inputIndex])
                    inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
                    inputSignature[0] = 0x30 # force for 1.4.9+
                    signatures.append(inputSignature)
                    inputIndex = inputIndex + 1
            else:
                while inputIndex < len(inputs):
                    self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
                                                            chipInputs, redeemScripts[inputIndex])
                    outputData = self.get_client().finalizeInputFull(txOutput)
                    outputData['outputData'] = txOutput
                    if firstTransaction:
                        transactionOutput = outputData['outputData']
                    if outputData['confirmationNeeded']:
                        outputData['address'] = output
                        self.handler.finished()
                        pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin
                        if not pin:
                            raise UserWarning()
                        if pin != 'paired':
                            self.handler.show_message(_("Confirmed. Signing Transaction..."))
                    else:
                        # Sign input with the provided PIN
                        inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
                        inputSignature[0] = 0x30 # force for 1.4.9+
                        signatures.append(inputSignature)
                        inputIndex = inputIndex + 1
                    if pin != 'paired':
                        firstTransaction = False
        except UserWarning:
            self.handler.show_error(_('Cancelled by user'))
            return
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            self.give_error(e, True)
        finally:
            self.handler.finished()

        for i, txin in enumerate(tx.inputs()):
            signingPos = inputs[i][4]
            txin['signatures'][signingPos] = bh2u(signatures[i])
        tx.raw = tx.serialize()
        self.signing = False
Example #46
0
    def settings_dialog(self):
        d = QDialog()
        d.setWindowTitle("Settings")
        layout = QGridLayout(d)
        layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
        layout.addWidget(QLabel(_('Currency: ')), 1, 0)
        layout.addWidget(QLabel(_('History Rates: ')), 2, 0)
        combo = QComboBox()
        combo_ex = QComboBox()
        hist_checkbox = QCheckBox()
        hist_checkbox.setEnabled(False)
        if self.config.get('history_rates', 'unchecked') == 'unchecked':
            hist_checkbox.setChecked(False)
        else:
            hist_checkbox.setChecked(True)
        ok_button = QPushButton(_("OK"))

        def on_change(x):
            try:
                cur_request = str(self.currencies[x])
            except Exception:
                return
            if cur_request != self.config.get('currency', "EUR"):
                self.config.set_key('currency', cur_request, True)
                cur_exchange = self.config.get('use_exchange', "Blockchain")
                if cur_request == "USD" and (cur_exchange == "CoinDesk"
                                             or cur_exchange == "Winkdex"):
                    hist_checkbox.setEnabled(True)
                elif cur_request == "VEF" and (cur_exchange
                                               == "BitcoinVenezuela"):
                    hist_checkbox.setEnabled(True)
                elif cur_request == "ARS" and (cur_exchange
                                               == "BitcoinVenezuela"):
                    hist_checkbox.setEnabled(True)
                else:
                    hist_checkbox.setChecked(False)
                    hist_checkbox.setEnabled(False)
                self.win.update_status()
                try:
                    self.fiat_button
                except:
                    pass
                else:
                    self.fiat_button.setText(cur_request)

        def disable_check():
            hist_checkbox.setChecked(False)
            hist_checkbox.setEnabled(False)

        def on_change_ex(x):
            cur_request = str(self.exchanges[x])
            if cur_request != self.config.get('use_exchange', "Blockchain"):
                self.config.set_key('use_exchange', cur_request, True)
                self.currencies = []
                combo.clear()
                self.exchanger.query_rates.set()
                cur_currency = self.config.get('currency', "EUR")
                if cur_request == "CoinDesk" or cur_request == "Winkdex":
                    if cur_currency == "USD":
                        hist_checkbox.setEnabled(True)
                    else:
                        disable_check()
                elif cur_request == "BitcoinVenezuela":
                    if cur_currency == "VEF" or cur_currency == "ARS":
                        hist_checkbox.setEnabled(True)
                    else:
                        disable_check()
                else:
                    disable_check()
                set_currencies(combo)
                self.win.update_status()

        def on_change_hist(checked):
            if checked:
                self.config.set_key('history_rates', 'checked')
                self.history_tab_update()
            else:
                self.config.set_key('history_rates', 'unchecked')
                self.gui.main_window.history_list.setHeaderLabels([
                    '',
                    _('Date'),
                    _('Description'),
                    _('Amount'),
                    _('Balance')
                ])
                self.gui.main_window.history_list.setColumnCount(5)
                for i, width in enumerate(
                        self.gui.main_window.column_widths['history']):
                    self.gui.main_window.history_list.setColumnWidth(i, width)

        def set_hist_check(hist_checkbox):
            cur_exchange = self.config.get('use_exchange', "Blockchain")
            if cur_exchange == "CoinDesk" or cur_exchange == "Winkdex":
                hist_checkbox.setEnabled(True)
            elif cur_exchange == "BitcoinVenezuela":
                hist_checkbox.setEnabled(True)
            else:
                hist_checkbox.setEnabled(False)

        def set_currencies(combo):
            current_currency = self.config.get('currency', "EUR")
            try:
                combo.clear()
            except Exception:
                return
            combo.addItems(self.currencies)
            try:
                index = self.currencies.index(current_currency)
            except Exception:
                index = 0
            combo.setCurrentIndex(index)

        def set_exchanges(combo_ex):
            try:
                combo_ex.clear()
            except Exception:
                return
            combo_ex.addItems(self.exchanges)
            try:
                index = self.exchanges.index(
                    self.config.get('use_exchange', "Blockchain"))
            except Exception:
                index = 0
            combo_ex.setCurrentIndex(index)

        def ok_clicked():
            d.accept()

        set_exchanges(combo_ex)
        set_currencies(combo)
        set_hist_check(hist_checkbox)
        combo.currentIndexChanged.connect(on_change)
        combo_ex.currentIndexChanged.connect(on_change_ex)
        hist_checkbox.stateChanged.connect(on_change_hist)
        combo.connect(self.win, SIGNAL('refresh_currencies_combo()'),
                      lambda: set_currencies(combo))
        combo_ex.connect(d, SIGNAL('refresh_exchanges_combo()'),
                         lambda: set_exchanges(combo_ex))
        ok_button.clicked.connect(lambda: ok_clicked())
        layout.addWidget(combo, 1, 1)
        layout.addWidget(combo_ex, 0, 1)
        layout.addWidget(hist_checkbox, 2, 1)
        layout.addWidget(ok_button, 3, 1)

        if d.exec_():
            return True
        else:
            return False
Example #47
0
from electrum.util import print_error, is_verbose, bfh, bh2u, versiontuple

try:
    import hid
    from btchip.btchipComm import HIDDongleHIDAPI, DongleWait
    from btchip.btchip import btchip
    from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script, get_p2sh_input_script
    from btchip.bitcoinTransaction import bitcoinTransaction
    from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware
    from btchip.btchipException import BTChipException
    BTCHIP = True
    BTCHIP_DEBUG = is_verbose
except ImportError:
    BTCHIP = False

MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
                      ' https://www.ledgerwallet.com'
MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Bitcoin" app) too old for Segwit support. Please update at') + \
                      ' https://www.ledgerwallet.com'
MULTI_OUTPUT_SUPPORT = '1.1.4'
SEGWIT_SUPPORT = '1.1.10'
SEGWIT_SUPPORT_SPECIAL = '1.0.4'


class Ledger_Client():
    def __init__(self, hidDevice):
        self.dongleObject = btchip(hidDevice)
        self.preflightDone = False

    def is_pairable(self):
        return True
Example #48
0
 def decrypt_message(self, sequence, message, password):
     raise UserFacingException(
         _('Encryption and decryption are not implemented by {}').format(
             self.device))
Example #49
0
    def create_menu(self, position):
        selected = self.get_selected_outpoints()
        if not selected:
            return
        menu = QMenu()
        menu.setSeparatorsCollapsible(
            True)  # consecutive separators are merged together
        coins = [self.utxo_dict[name] for name in selected]
        menu.addAction(_("Spend"), lambda: self.parent.spend_coins(coins))
        assert len(coins) >= 1, len(coins)
        if len(coins) == 1:
            utxo_dict = coins[0]
            addr = utxo_dict['address']
            txid = utxo_dict['prevout_hash']
            # "Details"
            tx = self.wallet.db.get_transaction(txid)
            if tx:
                label = self.wallet.get_label(
                    txid
                ) or None  # Prefer None if empty (None hides the Description: field in the window)
                menu.addAction(_("Details"),
                               lambda: self.parent.show_transaction(tx, label))
            # "Copy ..."
            idx = self.indexAt(position)
            if not idx.isValid():
                return
            col = idx.column()
            column_title = self.model().horizontalHeaderItem(col).text()
            copy_text = self.model().itemFromIndex(
                idx).text() if col != self.Columns.OUTPOINT else selected[0]
            if col == self.Columns.AMOUNT:
                copy_text = copy_text.strip()
            menu.addAction(
                _("Copy {}").format(column_title),
                lambda: self.parent.app.clipboard().setText(copy_text))
            # "Freeze coin"
            if not self.wallet.is_frozen_coin(utxo_dict):
                menu.addAction(
                    _("Freeze Coin"), lambda: self.parent.
                    set_frozen_state_of_coins([utxo_dict], True))
            else:
                menu.addSeparator()
                menu.addAction(_("Coin is frozen"),
                               lambda: None).setEnabled(False)
                menu.addAction(
                    _("Unfreeze Coin"), lambda: self.parent.
                    set_frozen_state_of_coins([utxo_dict], False))
                menu.addSeparator()
            # "Freeze address"
            if not self.wallet.is_frozen_address(addr):
                menu.addAction(
                    _("Freeze Address"), lambda: self.parent.
                    set_frozen_state_of_addresses([addr], True))
            else:
                menu.addSeparator()
                menu.addAction(_("Address is frozen"),
                               lambda: None).setEnabled(False)
                menu.addAction(
                    _("Unfreeze Address"), lambda: self.parent.
                    set_frozen_state_of_addresses([addr], False))
                menu.addSeparator()
        else:
            # multiple items selected
            menu.addSeparator()
            addrs = [utxo_dict['address'] for utxo_dict in coins]
            is_coin_frozen = [
                self.wallet.is_frozen_coin(utxo_dict) for utxo_dict in coins
            ]
            is_addr_frozen = [
                self.wallet.is_frozen_address(utxo_dict['address'])
                for utxo_dict in coins
            ]
            if not all(is_coin_frozen):
                menu.addAction(
                    _("Freeze Coins"),
                    lambda: self.parent.set_frozen_state_of_coins(coins, True))
            if any(is_coin_frozen):
                menu.addAction(
                    _("Unfreeze Coins"),
                    lambda: self.parent.set_frozen_state_of_coins(
                        coins, False))
            if not all(is_addr_frozen):
                menu.addAction(
                    _("Freeze Addresses"),
                    lambda: self.parent.set_frozen_state_of_addresses(
                        addrs, True))
            if any(is_addr_frozen):
                menu.addAction(
                    _("Unfreeze Addresses"),
                    lambda: self.parent.set_frozen_state_of_addresses(
                        addrs, False))

        menu.exec_(self.viewport().mapToGlobal(position))
Example #50
0
 def decrypt_message(self, pubkey, message, password):
     raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
Example #51
0
 def receive_list_menu(self, menu, addr):
     window = get_parent_main_window(menu)
     menu.addAction(_("Send via e-mail"), lambda: self.send(window, addr))
Example #52
0
class UTXOList(MyTreeView):
    class Columns(IntEnum):
        OUTPOINT = 0
        ADDRESS = 1
        LABEL = 2
        AMOUNT = 3
        HEIGHT = 4

    headers = {
        Columns.ADDRESS: _('Address'),
        Columns.LABEL: _('Label'),
        Columns.AMOUNT: _('Amount'),
        Columns.HEIGHT: _('Height'),
        Columns.OUTPOINT: _('Output point'),
    }
    filter_columns = [Columns.ADDRESS, Columns.LABEL]

    def __init__(self, parent=None):
        super().__init__(parent,
                         self.create_menu,
                         stretch_column=self.Columns.LABEL,
                         editable_columns=[])
        self.setModel(QStandardItemModel(self))
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSortingEnabled(True)
        self.update()

    def update(self):
        self.wallet = self.parent.wallet
        utxos = self.wallet.get_utxos()
        self.utxo_dict = {}
        self.model().clear()
        self.update_headers(self.__class__.headers)
        for idx, x in enumerate(utxos):
            self.insert_utxo(idx, x)
        self.filter()

    def insert_utxo(self, idx, x):
        address = x['address']
        height = x.get('height')
        name = x.get('prevout_hash') + ":%d" % x.get('prevout_n')
        name_short = x.get(
            'prevout_hash')[:16] + '...' + ":%d" % x.get('prevout_n')
        self.utxo_dict[name] = x
        label = self.wallet.get_label(x.get('prevout_hash'))
        amount = self.parent.format_amount(x['value'], whitespaces=True)
        labels = [name_short, address, label, amount, '%d' % height]
        utxo_item = [QStandardItem(x) for x in labels]
        self.set_editability(utxo_item)
        utxo_item[self.Columns.ADDRESS].setFont(QFont(MONOSPACE_FONT))
        utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT))
        utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT))
        utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole)
        if self.wallet.is_frozen_address(address):
            utxo_item[self.Columns.ADDRESS].setBackground(
                ColorScheme.BLUE.as_color(True))
            utxo_item[self.Columns.ADDRESS].setToolTip(_('Address is frozen'))
        if self.wallet.is_frozen_coin(x):
            utxo_item[self.Columns.OUTPOINT].setBackground(
                ColorScheme.BLUE.as_color(True))
            utxo_item[self.Columns.OUTPOINT].setToolTip(
                f"{name}\n{_('Coin is frozen')}")
        else:
            utxo_item[self.Columns.OUTPOINT].setToolTip(name)
        self.model().insertRow(idx, utxo_item)

    def get_selected_outpoints(self) -> Optional[List[str]]:
        if not self.model():
            return None
        items = self.selected_in_column(self.Columns.ADDRESS)
        if not items:
            return None
        return [x.data(Qt.UserRole) for x in items]

    def create_menu(self, position):
        selected = self.get_selected_outpoints()
        if not selected:
            return
        menu = QMenu()
        menu.setSeparatorsCollapsible(
            True)  # consecutive separators are merged together
        coins = [self.utxo_dict[name] for name in selected]
        menu.addAction(_("Spend"), lambda: self.parent.spend_coins(coins))
        assert len(coins) >= 1, len(coins)
        if len(coins) == 1:
            utxo_dict = coins[0]
            addr = utxo_dict['address']
            txid = utxo_dict['prevout_hash']
            # "Details"
            tx = self.wallet.db.get_transaction(txid)
            if tx:
                label = self.wallet.get_label(
                    txid
                ) or None  # Prefer None if empty (None hides the Description: field in the window)
                menu.addAction(_("Details"),
                               lambda: self.parent.show_transaction(tx, label))
            # "Copy ..."
            idx = self.indexAt(position)
            if not idx.isValid():
                return
            col = idx.column()
            column_title = self.model().horizontalHeaderItem(col).text()
            copy_text = self.model().itemFromIndex(
                idx).text() if col != self.Columns.OUTPOINT else selected[0]
            if col == self.Columns.AMOUNT:
                copy_text = copy_text.strip()
            menu.addAction(
                _("Copy {}").format(column_title),
                lambda: self.parent.app.clipboard().setText(copy_text))
            # "Freeze coin"
            if not self.wallet.is_frozen_coin(utxo_dict):
                menu.addAction(
                    _("Freeze Coin"), lambda: self.parent.
                    set_frozen_state_of_coins([utxo_dict], True))
            else:
                menu.addSeparator()
                menu.addAction(_("Coin is frozen"),
                               lambda: None).setEnabled(False)
                menu.addAction(
                    _("Unfreeze Coin"), lambda: self.parent.
                    set_frozen_state_of_coins([utxo_dict], False))
                menu.addSeparator()
            # "Freeze address"
            if not self.wallet.is_frozen_address(addr):
                menu.addAction(
                    _("Freeze Address"), lambda: self.parent.
                    set_frozen_state_of_addresses([addr], True))
            else:
                menu.addSeparator()
                menu.addAction(_("Address is frozen"),
                               lambda: None).setEnabled(False)
                menu.addAction(
                    _("Unfreeze Address"), lambda: self.parent.
                    set_frozen_state_of_addresses([addr], False))
                menu.addSeparator()
        else:
            # multiple items selected
            menu.addSeparator()
            addrs = [utxo_dict['address'] for utxo_dict in coins]
            is_coin_frozen = [
                self.wallet.is_frozen_coin(utxo_dict) for utxo_dict in coins
            ]
            is_addr_frozen = [
                self.wallet.is_frozen_address(utxo_dict['address'])
                for utxo_dict in coins
            ]
            if not all(is_coin_frozen):
                menu.addAction(
                    _("Freeze Coins"),
                    lambda: self.parent.set_frozen_state_of_coins(coins, True))
            if any(is_coin_frozen):
                menu.addAction(
                    _("Unfreeze Coins"),
                    lambda: self.parent.set_frozen_state_of_coins(
                        coins, False))
            if not all(is_addr_frozen):
                menu.addAction(
                    _("Freeze Addresses"),
                    lambda: self.parent.set_frozen_state_of_addresses(
                        addrs, True))
            if any(is_addr_frozen):
                menu.addAction(
                    _("Unfreeze Addresses"),
                    lambda: self.parent.set_frozen_state_of_addresses(
                        addrs, False))

        menu.exec_(self.viewport().mapToGlobal(position))
Example #53
0
 def decrypt_message(self, sequence, message, password):
     raise RuntimeError(
         _('Encryption and decryption are not implemented by {}').format(
             self.device))
Example #54
0
 def verify_seed(self, seed, is_valid=None):
     while True:
         r = self.request_seed(MSG_VERIFY_SEED, is_valid)
         if prepare_seed(r) == prepare_seed(seed):
             return
         self.show_error(_('Incorrect seed'))
Example #55
0
    def show_summary(self):
        fx = self.parent.fx
        show_fiat = fx and fx.is_enabled() and fx.get_history_config()
        if not show_fiat:
            self.parent.show_message(
                _("Enable fiat exchange rate with history."))
            return
        h = self.wallet.get_detailed_history(
            from_timestamp=time.mktime(self.start_date.timetuple())
            if self.start_date else None,
            to_timestamp=time.mktime(self.end_date.timetuple())
            if self.end_date else None,
            fx=fx)
        summary = h['summary']
        if not summary:
            self.parent.show_message(_("Nothing to summarize."))
            return
        start = summary['begin']
        end = summary['end']
        flow = summary['flow']
        start_date = start.get('date')
        end_date = end.get('date')
        format_amount = lambda x: self.parent.format_amount(
            x.value) + ' ' + self.parent.base_unit()
        format_fiat = lambda x: str(x) + ' ' + self.parent.fx.ccy

        d = WindowModalDialog(self, _("Summary"))
        d.setMinimumSize(600, 150)
        vbox = QVBoxLayout()
        msg = messages.to_rtf(messages.MSG_CAPITAL_GAINS)
        vbox.addWidget(WWLabel(msg))
        grid = QGridLayout()
        grid.addWidget(QLabel(_("Begin")), 0, 1)
        grid.addWidget(QLabel(_("End")), 0, 2)
        #
        grid.addWidget(QLabel(_("Date")), 1, 0)
        grid.addWidget(QLabel(self.format_date(start_date)), 1, 1)
        grid.addWidget(QLabel(self.format_date(end_date)), 1, 2)
        #
        grid.addWidget(QLabel(_("BTC balance")), 2, 0)
        grid.addWidget(QLabel(format_amount(start['BTC_balance'])), 2, 1)
        grid.addWidget(QLabel(format_amount(end['BTC_balance'])), 2, 2)
        #
        grid.addWidget(QLabel(_("BTC Fiat price")), 3, 0)
        grid.addWidget(QLabel(format_fiat(start.get('BTC_fiat_price'))), 3, 1)
        grid.addWidget(QLabel(format_fiat(end.get('BTC_fiat_price'))), 3, 2)
        #
        grid.addWidget(QLabel(_("Fiat balance")), 4, 0)
        grid.addWidget(QLabel(format_fiat(start.get('fiat_balance'))), 4, 1)
        grid.addWidget(QLabel(format_fiat(end.get('fiat_balance'))), 4, 2)
        #
        grid.addWidget(QLabel(_("Acquisition price")), 5, 0)
        grid.addWidget(QLabel(format_fiat(start.get('acquisition_price', ''))),
                       5, 1)
        grid.addWidget(QLabel(format_fiat(end.get('acquisition_price', ''))),
                       5, 2)
        #
        grid.addWidget(QLabel(_("Unrealized capital gains")), 6, 0)
        grid.addWidget(QLabel(format_fiat(start.get('unrealized_gains', ''))),
                       6, 1)
        grid.addWidget(QLabel(format_fiat(end.get('unrealized_gains', ''))), 6,
                       2)
        #
        grid2 = QGridLayout()
        grid2.addWidget(QLabel(_("BTC incoming")), 0, 0)
        grid2.addWidget(QLabel(format_amount(flow['BTC_incoming'])), 0, 1)
        grid2.addWidget(QLabel(_("Fiat incoming")), 1, 0)
        grid2.addWidget(QLabel(format_fiat(flow.get('fiat_incoming'))), 1, 1)
        grid2.addWidget(QLabel(_("BTC outgoing")), 2, 0)
        grid2.addWidget(QLabel(format_amount(flow['BTC_outgoing'])), 2, 1)
        grid2.addWidget(QLabel(_("Fiat outgoing")), 3, 0)
        grid2.addWidget(QLabel(format_fiat(flow.get('fiat_outgoing'))), 3, 1)
        #
        grid2.addWidget(QLabel(_("Realized capital gains")), 4, 0)
        grid2.addWidget(
            QLabel(format_fiat(flow.get('realized_capital_gains'))), 4, 1)
        vbox.addLayout(grid)
        vbox.addWidget(QLabel(_('Cash flow')))
        vbox.addLayout(grid2)
        vbox.addLayout(Buttons(CloseButton(d)))
        d.setLayout(vbox)
        d.exec_()
Example #56
0
 def description(self):
     return _("Send and receive payment requests via email")
Example #57
0
    def get_data_for_role(self, index: QModelIndex,
                          role: Qt.ItemDataRole) -> QVariant:
        # note: this method is performance-critical.
        # it is called a lot, and so must run extremely fast.
        assert index.isValid()
        col = index.column()
        window = self.model.parent
        tx_item = self.get_data()
        is_lightning = tx_item.get('lightning', False)
        timestamp = tx_item['timestamp']
        if is_lightning:
            status = 0
            if timestamp is None:
                status_str = 'unconfirmed'
            else:
                status_str = format_time(int(timestamp))
        else:
            tx_hash = tx_item['txid']
            conf = tx_item['confirmations']
            try:
                status, status_str = self.model.tx_status_cache[tx_hash]
            except KeyError:
                tx_mined_info = self.model.tx_mined_info_from_tx_item(tx_item)
                status, status_str = window.wallet.get_tx_status(
                    tx_hash, tx_mined_info)

        if role == ROLE_SORT_ORDER:
            d = {
                HistoryColumns.STATUS:
                    # respect sort order of self.transactions (wallet.get_full_history)
                    -index.row(),
                HistoryColumns.DESCRIPTION:
                    tx_item['label'] if 'label' in tx_item else None,
                HistoryColumns.AMOUNT:
                    (tx_item['bc_value'].value if 'bc_value' in tx_item else 0)\
                    + (tx_item['ln_value'].value if 'ln_value' in tx_item else 0),
                HistoryColumns.BALANCE:
                    (tx_item['balance'].value if 'balance' in tx_item else 0),
                HistoryColumns.FIAT_VALUE:
                    tx_item['fiat_value'].value if 'fiat_value' in tx_item else None,
                HistoryColumns.FIAT_ACQ_PRICE:
                    tx_item['acquisition_price'].value if 'acquisition_price' in tx_item else None,
                HistoryColumns.FIAT_CAP_GAINS:
                    tx_item['capital_gain'].value if 'capital_gain' in tx_item else None,
                HistoryColumns.TXID: tx_hash if not is_lightning else None,
            }
            return QVariant(d[col])
        if role == MyTreeView.ROLE_EDIT_KEY:
            return QVariant(get_item_key(tx_item))
        if role not in (Qt.DisplayRole, Qt.EditRole):
            if col == HistoryColumns.STATUS and role == Qt.DecorationRole:
                icon = "lightning" if is_lightning else TX_ICONS[status]
                return QVariant(read_QIcon(icon))
            elif col == HistoryColumns.STATUS and role == Qt.ToolTipRole:
                if is_lightning:
                    msg = 'lightning transaction'
                else:  # on-chain
                    if tx_item['height'] == TX_HEIGHT_LOCAL:
                        # note: should we also explain double-spends?
                        msg = _(
                            "This transaction is only available on your local machine.\n"
                            "The currently connected server does not know about it.\n"
                            "You can either broadcast it now, or simply remove it."
                        )
                    else:
                        msg = str(conf) + _(" confirmation" +
                                            ("s" if conf != 1 else ""))
                return QVariant(msg)
            elif col > HistoryColumns.DESCRIPTION and role == Qt.TextAlignmentRole:
                return QVariant(int(Qt.AlignRight | Qt.AlignVCenter))
            elif col > HistoryColumns.DESCRIPTION and role == Qt.FontRole:
                monospace_font = QFont(MONOSPACE_FONT)
                return QVariant(monospace_font)
            #elif col == HistoryColumns.DESCRIPTION and role == Qt.DecorationRole and not is_lightning\
            #        and self.parent.wallet.invoices.paid.get(tx_hash):
            #    return QVariant(read_QIcon("seal"))
            elif col in (HistoryColumns.DESCRIPTION, HistoryColumns.AMOUNT) \
                    and role == Qt.ForegroundRole and tx_item['value'].value < 0:
                red_brush = QBrush(QColor("#BC1E1E"))
                return QVariant(red_brush)
            elif col == HistoryColumns.FIAT_VALUE and role == Qt.ForegroundRole \
                    and not tx_item.get('fiat_default') and tx_item.get('fiat_value') is not None:
                blue_brush = QBrush(QColor("#1E1EFF"))
                return QVariant(blue_brush)
            return QVariant()
        if col == HistoryColumns.STATUS:
            return QVariant(status_str)
        elif col == HistoryColumns.DESCRIPTION and 'label' in tx_item:
            return QVariant(tx_item['label'])
        elif col == HistoryColumns.AMOUNT:
            bc_value = tx_item['bc_value'].value if 'bc_value' in tx_item else 0
            ln_value = tx_item['ln_value'].value if 'ln_value' in tx_item else 0
            value = bc_value + ln_value
            v_str = window.format_amount(value, is_diff=True, whitespaces=True)
            return QVariant(v_str)
        elif col == HistoryColumns.BALANCE:
            balance = tx_item['balance'].value
            balance_str = window.format_amount(balance, whitespaces=True)
            return QVariant(balance_str)
        elif col == HistoryColumns.FIAT_VALUE and 'fiat_value' in tx_item:
            value_str = window.fx.format_fiat(tx_item['fiat_value'].value)
            return QVariant(value_str)
        elif col == HistoryColumns.FIAT_ACQ_PRICE and \
                tx_item['value'].value < 0 and 'acquisition_price' in tx_item:
            # fixme: should use is_mine
            acq = tx_item['acquisition_price'].value
            return QVariant(window.fx.format_fiat(acq))
        elif col == HistoryColumns.FIAT_CAP_GAINS and 'capital_gain' in tx_item:
            cg = tx_item['capital_gain'].value
            return QVariant(window.fx.format_fiat(cg))
        elif col == HistoryColumns.TXID:
            return QVariant(tx_hash) if not is_lightning else QVariant('')
        return QVariant()
Example #58
0
 def create_menu(self, position: QPoint):
     org_idx: QModelIndex = self.indexAt(position)
     idx = self.proxy.mapToSource(org_idx)
     if not idx.isValid():
         # can happen e.g. before list is populated for the first time
         return
     tx_item = idx.internalPointer().get_data()
     if tx_item.get('lightning') and tx_item['type'] == 'payment':
         menu = QMenu()
         menu.addAction(
             _("View Payment"),
             lambda: self.parent.show_lightning_transaction(tx_item))
         cc = self.add_copy_menu(menu, idx)
         cc.addAction(
             _("Payment Hash"),
             lambda: self.place_text_on_clipboard(tx_item['payment_hash'],
                                                  title="Payment Hash"))
         cc.addAction(
             _("Preimage"),
             lambda: self.place_text_on_clipboard(tx_item['preimage'],
                                                  title="Preimage"))
         key = tx_item['payment_hash']
         log = self.wallet.lnworker.logs.get(key)
         if log:
             menu.addAction(
                 _("View log"),
                 lambda: self.parent.invoice_list.show_log(key, log))
         menu.exec_(self.viewport().mapToGlobal(position))
         return
     tx_hash = tx_item['txid']
     if tx_item.get('lightning'):
         tx = self.wallet.adb.get_transaction(tx_hash)
     else:
         tx = self.wallet.db.get_transaction(tx_hash)
     if not tx:
         return
     tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
     tx_details = self.wallet.get_tx_info(tx)
     is_unconfirmed = tx_details.tx_mined_status.height <= 0
     menu = QMenu()
     if tx_details.can_remove:
         menu.addAction(_("Remove"), lambda: self.remove_local_tx(tx_hash))
     cc = self.add_copy_menu(menu, idx)
     cc.addAction(
         _("Transaction ID"),
         lambda: self.place_text_on_clipboard(tx_hash, title="TXID"))
     for c in self.editable_columns:
         if self.isColumnHidden(c): continue
         label = self.hm.headerData(c, Qt.Horizontal, Qt.DisplayRole)
         # TODO use siblingAtColumn when min Qt version is >=5.11
         persistent = QPersistentModelIndex(
             org_idx.sibling(org_idx.row(), c))
         menu.addAction(_("Edit {}").format(label),
                        lambda p=persistent: self.edit(QModelIndex(p)))
     menu.addAction(_("View Transaction"),
                    lambda: self.show_transaction(tx_item, tx))
     channel_id = tx_item.get('channel_id')
     if channel_id:
         menu.addAction(
             _("View Channel"),
             lambda: self.parent.show_channel(bytes.fromhex(channel_id)))
     if is_unconfirmed and tx:
         if tx_details.can_bump:
             menu.addAction(_("Increase fee"),
                            lambda: self.parent.bump_fee_dialog(tx))
         else:
             if tx_details.can_cpfp:
                 menu.addAction(_("Child pays for parent"),
                                lambda: self.parent.cpfp_dialog(tx))
         if tx_details.can_dscancel:
             menu.addAction(_("Cancel (double-spend)"),
                            lambda: self.parent.dscancel_dialog(tx))
     invoices = self.wallet.get_relevant_invoices_for_tx(tx)
     if len(invoices) == 1:
         menu.addAction(
             _("View invoice"),
             lambda inv=invoices[0]: self.parent.show_onchain_invoice(inv))
     elif len(invoices) > 1:
         menu_invs = menu.addMenu(_("Related invoices"))
         for inv in invoices:
             menu_invs.addAction(
                 _("View invoice"),
                 lambda inv=inv: self.parent.show_onchain_invoice(inv))
     if tx_URL:
         menu.addAction(_("View on block explorer"),
                        lambda: webopen(tx_URL))
     menu.exec_(self.viewport().mapToGlobal(position))
Example #59
0
class GuiMixin(object):
    # Requires: self.proto, self.device

    messages = {
        3:
        _("Confirm the transaction output on your {} device"),
        4:
        _("Confirm internal entropy on your {} device to begin"),
        5:
        _("Write down the seed word shown on your {}"),
        6:
        _("Confirm on your {} that you want to wipe it clean"),
        7:
        _("Confirm on your {} device the message to sign"),
        8:
        _("Confirm the total amount spent and the transaction fee on your "
          "{} device"),
        10:
        _("Confirm wallet address on your {} device"),
        'default':
        _("Check your {} device to continue"),
    }

    def callback_Failure(self, msg):
        # BaseClient's unfortunate call() implementation forces us to
        # raise exceptions on failure in order to unwind the stack.
        # However, making the user acknowledge they cancelled
        # gets old very quickly, so we suppress those.  The NotInitialized
        # one is misnamed and indicates a passphrase request was cancelled.
        if msg.code in (self.types.FailureType.PinCancelled,
                        self.types.FailureType.ActionCancelled,
                        self.types.FailureType.NotInitialized):
            raise UserCancelled()
        raise RuntimeError(msg.message)

    def callback_ButtonRequest(self, msg):
        message = self.msg
        if not message:
            message = self.messages.get(msg.code, self.messages['default'])
        self.handler.show_message(message.format(self.device), self.cancel)
        return self.proto.ButtonAck()

    def callback_PinMatrixRequest(self, msg):
        if msg.type == 2:
            msg = _("Enter a new PIN for your {}:")
        elif msg.type == 3:
            msg = (_("Re-enter the new PIN for your {}.\n\n"
                     "NOTE: the positions of the numbers have changed!"))
        else:
            msg = _("Enter your current {} PIN:")
        pin = self.handler.get_pin(msg.format(self.device))
        if len(pin) > 9:
            self.handler.show_error(
                _('The PIN cannot be longer than 9 characters.'))
            pin = ''  # to cancel below
        if not pin:
            return self.proto.Cancel()
        return self.proto.PinMatrixAck(pin=pin)

    def callback_PassphraseRequest(self, req):
        if req and hasattr(req, 'on_device') and req.on_device is True:
            return self.proto.PassphraseAck()

        if self.creating_wallet:
            msg = _("Enter a passphrase to generate this wallet.  Each time "
                    "you use this wallet your {} will prompt you for the "
                    "passphrase.  If you forget the passphrase you cannot "
                    "access the bitcoins in the wallet.").format(self.device)
        else:
            msg = _("Enter the passphrase to unlock this wallet:")
        passphrase = self.handler.get_passphrase(msg, self.creating_wallet)
        if passphrase is None:
            return self.proto.Cancel()
        passphrase = bip39_normalize_passphrase(passphrase)
        return self.proto.PassphraseAck(passphrase=passphrase)

    def callback_PassphraseStateRequest(self, msg):
        return self.proto.PassphraseStateAck()

    def callback_WordRequest(self, msg):
        self.step += 1
        msg = _("Step {}/24.  Enter seed word as explained on "
                "your {}:").format(self.step, self.device)
        word = self.handler.get_word(msg)
        # Unfortunately the device can't handle self.proto.Cancel()
        return self.proto.WordAck(word=word)

    def callback_CharacterRequest(self, msg):
        char_info = self.handler.get_char(msg)
        if not char_info:
            return self.proto.Cancel()
        return self.proto.CharacterAck(**char_info)
Example #60
0
 def format_date(self, d):
     return str(datetime.date(d.year, d.month, d.day)) if d else _('None')