Exemplo n.º 1
0
 def __init__(self, config, daemon, plugins):
     set_language(config.get('language'))
     # Uncomment this call to verify objects are being properly
     # GC-ed when windows are closed
     #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
     #                            ElectrumWindow], interval=5)])
     QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
     if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
         QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
     if hasattr(QGuiApplication, 'setDesktopFileName'):
         QGuiApplication.setDesktopFileName('electrum.desktop')
     self.config = config
     self.daemon = daemon
     self.plugins = plugins
     self.windows = []
     self.efilter = OpenFileEventFilter(self.windows)
     self.app = QElectrumApplication(sys.argv)
     self.app.installEventFilter(self.efilter)
     self.timer = Timer()
     self.nd = None
     self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
     self._num_wizards_in_progress = 0
     self._num_wizards_lock = threading.Lock()
     # init tray
     self.dark_icon = self.config.get("dark_icon", False)
     self.tray = QSystemTrayIcon(self.tray_icon(), None)
     self.tray.setToolTip('Electrum')
     self.tray.activated.connect(self.tray_activated)
     self.build_tray_menu()
     self.tray.show()
     self.app.new_window_signal.connect(self.start_new_window)
     self.set_dark_theme_if_needed()
     run_hook('init_qt', self)
Exemplo n.º 2
0
 def create_menu(self, position):
     idx = self.indexAt(position)
     item = self.model().itemFromIndex(idx)
     # TODO use siblingAtColumn when min Qt version is >=5.11
     item_addr = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.ADDRESS))
     if not item_addr:
         return
     addr = item_addr.text()
     req = self.wallet.receive_requests.get(addr)
     if req is None:
         self.update()
         return
     column = idx.column()
     column_title = self.model().horizontalHeaderItem(column).text()
     column_data = item.text()
     menu = QMenu(self)
     if column != self.Columns.SIGNATURE:
         if column == self.Columns.AMOUNT:
             column_data = column_data.strip()
         menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
     menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))
     menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
     menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
     run_hook('receive_list_menu', menu, addr)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 3
0
    def create_menu(self, position):
        menu = QMenu()
        selected = self.selected_in_column(0)
        selected_keys = []
        for idx in selected:
            sel_key = self.model().itemFromIndex(idx).data(Qt.UserRole)
            selected_keys.append(sel_key)
        idx = self.indexAt(position)
        if not selected or not idx.isValid():
            menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
            menu.addAction(_("Import file"), lambda: self.import_contacts())
            menu.addAction(_("Export file"), lambda: self.export_contacts())
        else:
            column = idx.column()
            column_title = self.model().horizontalHeaderItem(column).text()
            column_data = '\n'.join(self.model().itemFromIndex(idx).text() for idx in selected)
            menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
            if column in self.editable_columns:
                item = self.model().itemFromIndex(idx)
                if item.isEditable():
                    # would not be editable if openalias
                    persistent = QPersistentModelIndex(idx)
                    menu.addAction(_("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p)))
            menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(selected_keys))
            menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(selected_keys))
            URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, selected_keys)]
            if URLs:
                menu.addAction(_("View on block explorer"), lambda: map(webbrowser.open, URLs))

        run_hook('create_contact_menu', menu, selected_keys)
        menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 4
0
 def create_window_for_wallet(self, wallet):
     w = ElectrumWindow(self, wallet)
     self.windows.append(w)
     self.build_tray_menu()
     # FIXME: Remove in favour of the load_wallet hook
     run_hook('on_new_window', w)
     return w
Exemplo n.º 5
0
    def __init__(self, text=None):
        ButtonsTextEdit.__init__(self, text)
        self.setReadOnly(1)
        icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
        self.addButton(icon, self.qr_show, _("Show as QR code"))

        run_hook('show_text_edit', self)
Exemplo n.º 6
0
    def create_menu(self, position):
        menu = QMenu()
        selected = self.selectedItems()
        if not selected:
            menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
            menu.addAction(_("Import file"), lambda: self.import_contacts())
            menu.addAction(_("Export file"), lambda: self.export_contacts())
        else:
            names = [item.text(0) for item in selected]
            keys = [item.text(1) for item in selected]
            column = self.currentColumn()
            column_title = self.headerItem().text(column)
            column_data = '\n'.join([item.text(column) for item in selected])
            menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
            if column in self.editable_columns:
                item = self.currentItem()
                menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, column))
            menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(keys))
            menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(keys))
            URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, keys)]
            if URLs:
                menu.addAction(_("View on block explorer"), lambda: map(webbrowser.open, URLs))

        run_hook('create_contact_menu', menu, selected)
        menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 7
0
 def close_window(self, window):
     if window in self.windows:
        self.windows.remove(window)
     self.build_tray_menu()
     # save wallet path of last open window
     if not self.windows:
         self.config.save_last_wallet(window.wallet)
     run_hook('on_close_window', window)
Exemplo n.º 8
0
 def __init__(self, text="", allow_multi=False):
     ButtonsTextEdit.__init__(self, text)
     self.allow_multi = allow_multi
     self.setReadOnly(0)
     self.addButton(":icons/file.png", self.file_input, _("Read file"))
     icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png"
     self.addButton(icon, self.qr_input, _("Read QR code"))
     run_hook('scan_text_edit', self)
Exemplo n.º 9
0
 def __init__(self, parent, seed, passphrase):
     WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
     self.setMinimumWidth(400)
     vbox = QVBoxLayout(self)
     title =  _("Your wallet generation seed is:")
     slayout = SeedLayout(title=title, seed=seed, msg=True, passphrase=passphrase)
     vbox.addLayout(slayout)
     has_extension = True if passphrase else False
     run_hook('set_seed', seed, has_extension, slayout.seed_e)
     vbox.addLayout(Buttons(CloseButton(self)))
Exemplo n.º 10
0
 def load_wallet(self, wallet):
     if self.wallet:
         self.stop_wallet()
     self.wallet = wallet
     self.update_wallet()
     # Once GUI has been initialized check if we want to announce something
     # since the callback has been called before the GUI was initialized
     if self.receive_screen:
         self.receive_screen.clear()
     self.update_tabs()
     run_hook('load_wallet', wallet, self)
Exemplo n.º 11
0
 def on_update(self):
     item = self.currentItem()
     current_key = item.data(0, Qt.UserRole) if item else None
     self.clear()
     for key in sorted(self.parent.contacts.keys()):
         _type, name = self.parent.contacts[key]
         item = QTreeWidgetItem([name, key])
         item.setData(0, Qt.UserRole, key)
         self.addTopLevelItem(item)
         if key == current_key:
             self.setCurrentItem(item)
     run_hook('update_contacts_tab', self)
Exemplo n.º 12
0
    def create_menu(self, position):
        from electrum.wallet import Multisig_Wallet
        is_multisig = isinstance(self.wallet, Multisig_Wallet)
        can_delete = self.wallet.can_delete_address()
        selected = self.selected_in_column(self.Columns.ADDRESS)
        if not selected:
            return
        multi_select = len(selected) > 1
        addrs = [self.model().itemFromIndex(item).text() for item in selected]
        menu = QMenu()
        if not multi_select:
            idx = self.indexAt(position)
            col = idx.column()
            item = self.model().itemFromIndex(idx)
            if not item:
                return
            addr = addrs[0]

            addr_column_title = self.model().horizontalHeaderItem(self.Columns.LABEL).text()
            addr_idx = idx.sibling(idx.row(), self.Columns.LABEL)

            column_title = self.model().horizontalHeaderItem(col).text()
            copy_text = self.model().itemFromIndex(idx).text()
            if col == self.Columns.COIN_BALANCE or col == self.Columns.FIAT_BALANCE:
                copy_text = copy_text.strip()
            menu.addAction(_("Copy {}").format(column_title), lambda: self.place_text_on_clipboard(copy_text))
            menu.addAction(_('Details'), lambda: self.parent.show_address(addr))
            persistent = QPersistentModelIndex(addr_idx)
            menu.addAction(_("Edit {}").format(addr_column_title), lambda p=persistent: self.edit(QModelIndex(p)))
            menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr))
            if self.wallet.can_export():
                menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr))
            if not is_multisig and not self.wallet.is_watching_only():
                menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
                menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
            if can_delete:
                menu.addAction(_("Remove from wallet"), lambda: self.parent.remove_address(addr))
            addr_URL = block_explorer_URL(self.config, 'addr', addr)
            if addr_URL:
                menu.addAction(_("View on block explorer"), lambda: webbrowser.open(addr_URL))

            if not self.wallet.is_frozen_address(addr):
                menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], True))
            else:
                menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], False))

        coins = self.wallet.get_spendable_coins(addrs, config=self.config)
        if coins:
            menu.addAction(_("Spend from"), lambda: self.parent.spend_coins(coins))

        run_hook('receive_menu', menu, addrs, self.wallet)
        menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 13
0
    def create_menu(self, position):
        from electrum.wallet import Multisig_Wallet
        is_multisig = isinstance(self.wallet, Multisig_Wallet)
        can_delete = self.wallet.can_delete_address()
        selected = self.selectedItems()
        multi_select = len(selected) > 1
        addrs = [item.text(1) for item in selected]
        if not addrs:
            return
        if not multi_select:
            item = self.itemAt(position)
            col = self.currentColumn()
            if not item:
                return
            addr = addrs[0]
            if not is_address(addr):
                item.setExpanded(not item.isExpanded())
                return

        menu = QMenu()
        if not multi_select:
            column_title = self.headerItem().text(col)
            copy_text = item.text(col)
            menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(copy_text))
            menu.addAction(_('Details'), lambda: self.parent.show_address(addr))
            if col in self.editable_columns:
                menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, col))
            menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr))
            if self.wallet.can_export():
                menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr))
            if not is_multisig and not self.wallet.is_watching_only():
                menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
                menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
            if can_delete:
                menu.addAction(_("Remove from wallet"), lambda: self.parent.remove_address(addr))
            addr_URL = block_explorer_URL(self.config, 'addr', addr)
            if addr_URL:
                menu.addAction(_("View on block explorer"), lambda: webbrowser.open(addr_URL))

            if not self.wallet.is_frozen(addr):
                menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state([addr], True))
            else:
                menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state([addr], False))

        coins = self.wallet.get_utxos(addrs)
        if coins:
            menu.addAction(_("Spend from"), lambda: self.parent.spend_coins(coins))

        run_hook('receive_menu', menu, addrs, self.wallet)
        menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 14
0
 def __init__(self, parent=None, msg=None):
     msg = msg or _('Please enter your password')
     WindowModalDialog.__init__(self, parent, _("Enter Password"))
     self.pw = pw = QLineEdit()
     pw.setEchoMode(2)
     vbox = QVBoxLayout()
     vbox.addWidget(QLabel(msg))
     grid = QGridLayout()
     grid.setSpacing(8)
     grid.addWidget(QLabel(_('Password')), 1, 0)
     grid.addWidget(pw, 1, 1)
     vbox.addLayout(grid)
     vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))
     self.setLayout(vbox)
     run_hook('password_dialog', pw, grid, 1)
Exemplo n.º 15
0
    def _do_send(self, amount, message, outputs, rbf):
        # make unsigned transaction
        config = self.app.electrum_config
        coins = self.app.wallet.get_spendable_coins(None, config)
        try:
            tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None)
        except NotEnoughFunds:
            self.app.show_error(_("Not enough funds"))
            return
        except Exception as e:
            traceback.print_exc(file=sys.stdout)
            self.app.show_error(str(e))
            return
        if rbf:
            tx.set_rbf(True)
        fee = tx.get_fee()
        msg = [
            _("Amount to be sent") + ": " + self.app.format_amount_and_units(amount),
            _("Mining fee") + ": " + self.app.format_amount_and_units(fee),
        ]
        x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            msg.append(_("Additional fees") + ": " + self.app.format_amount_and_units(x_fee_amount))

        if fee >= config.get('confirm_fee', 100000):
            msg.append(_('Warning')+ ': ' + _("The fee for this transaction seems unusually high."))
        msg.append(_("Enter your PIN code to proceed"))
        self.app.protected('\n'.join(msg), self.send_tx, (tx, message))
Exemplo n.º 16
0
 def load_wallet(self, wallet):
     if self.wallet:
         self.stop_wallet()
     self.wallet = wallet
     self.wallet_name = wallet.basename()
     self.update_wallet()
     # Once GUI has been initialized check if we want to announce something
     # since the callback has been called before the GUI was initialized
     if self.receive_screen:
         self.receive_screen.clear()
     self.update_tabs()
     run_hook('load_wallet', wallet, self)
     try:
         wallet.try_detecting_internal_addresses_corruption()
     except InternalAddressCorruption as e:
         self.show_error(str(e))
         send_exception_to_crash_reporter(e)
Exemplo n.º 17
0
 def _sign_tx(self, tx, password, on_success, on_failure):
     try:
         self.wallet.sign_transaction(tx, password)
     except InvalidPassword:
         Clock.schedule_once(lambda dt: on_failure(_("Invalid PIN")))
         return
     on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
     Clock.schedule_once(lambda dt: on_success(tx))
Exemplo n.º 18
0
    def update(self):
        desc = self.desc
        base_unit = self.main_window.base_unit()
        format_amount = self.main_window.format_amount
        tx_hash, status, label, can_broadcast, can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
        size = self.tx.estimated_size()
        self.broadcast_button.setEnabled(can_broadcast)
        can_sign = not self.tx.is_complete() and \
            (self.wallet.can_sign(self.tx) or bool(self.main_window.tx_external_keypairs))
        self.sign_button.setEnabled(can_sign)
        self.tx_hash_e.setText(tx_hash or _('Unknown'))
        if desc is None:
            self.tx_desc.hide()
        else:
            self.tx_desc.setText(_("Description") + ': ' + desc)
            self.tx_desc.show()
        self.status_label.setText(_('Status:') + ' ' + status)

        if timestamp:
            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
            self.date_label.setText(_("Date: {}").format(time_str))
            self.date_label.show()
        elif exp_n:
            text = '%.2f MB'%(exp_n/1000000)
            self.date_label.setText(_('Position in mempool: {} from tip').format(text))
            self.date_label.show()
        else:
            self.date_label.hide()
        if amount is None:
            amount_str = _("Transaction unrelated to your wallet")
        elif amount > 0:
            amount_str = _("Amount received:") + ' %s'% format_amount(amount) + ' ' + base_unit
        else:
            amount_str = _("Amount sent:") + ' %s'% format_amount(-amount) + ' ' + base_unit
        size_str = _("Size:") + ' %d bytes'% size
        fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown'))
        if fee is not None:
            fee_rate = fee/size*1000
            fee_str += '  ( %s ) ' % self.main_window.format_fee_rate(fee_rate)
            confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
            if fee_rate > confirm_rate:
                fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!'
        self.amount_label.setText(amount_str)
        self.fee_label.setText(fee_str)
        self.size_label.setText(size_str)
        run_hook('transaction_dialog_update', self)
Exemplo n.º 19
0
 def update(self):
     current_key = self.current_item_user_role(col=0)
     self.model().clear()
     self.update_headers([_('Name'), _('Address')])
     set_current = None
     for key in sorted(self.parent.contacts.keys()):
         contact_type, name = self.parent.contacts[key]
         items = [QStandardItem(x) for x in (name, key)]
         items[0].setEditable(contact_type != 'openalias')
         items[1].setEditable(False)
         items[0].setData(key, Qt.UserRole)
         row_count = self.model().rowCount()
         self.model().insertRow(row_count, items)
         if key == current_key:
             idx = self.model().index(row_count, 0)
             set_current = QPersistentModelIndex(idx)
     self.set_current_idx(set_current)
     run_hook('update_contacts_tab', self)
Exemplo n.º 20
0
 def do_clear(self):
     self._lnurl_data = None
     self.send_button.restore_original_text()
     self.max_button.setChecked(False)
     self.payment_request = None
     self.payto_URI = None
     self.payto_e.do_clear()
     self.set_onchain(False)
     for e in [self.message_e, self.amount_e]:
         e.setText('')
         e.setFrozen(False)
     for e in [
             self.send_button, self.save_button, self.clear_button,
             self.amount_e, self.fiat_send_e
     ]:
         e.setEnabled(True)
     self.window.update_status()
     run_hook('do_clear', self)
Exemplo n.º 21
0
 def on_update(self):
     item = self.currentItem()
     current_key = item.data(0, Qt.UserRole) if item else None
     self.clear()
     for key in sorted(self.parent.tokens.keys()):
         token = self.parent.tokens[key]
         balance_str = '{}'.format(token.balance / 10**token.decimals)
         # balance_str = format_satoshis(token.balance, is_diff=False, num_zeros=0,
         #                               decimal_point=token.decimals, whitespaces=True)
         item = QTreeWidgetItem([token.name, token.bind_addr, balance_str])
         item.setData(0, Qt.UserRole, token.contract_addr)
         item.setTextAlignment(0, Qt.AlignLeft | Qt.AlignVCenter)
         item.setTextAlignment(2, Qt.AlignRight | Qt.AlignVCenter)
         item.setFont(2, QFont(MONOSPACE_FONT))
         self.addTopLevelItem(item)
         if key == current_key:
             self.setCurrentItem(item)
     run_hook('update_tokens_tab', self)
Exemplo n.º 22
0
 def update(self):
     current_key = self.current_item_user_role(col=0)
     self.model().clear()
     self.update_headers([_('Name'), _('Address')])
     set_current = None
     for key in sorted(self.parent.contacts.keys()):
         contact_type, name = self.parent.contacts[key]
         items = [QStandardItem(x) for x in (name, key)]
         items[0].setEditable(contact_type != 'openalias')
         items[1].setEditable(False)
         items[0].setData(key, Qt.UserRole)
         row_count = self.model().rowCount()
         self.model().insertRow(row_count, items)
         if key == current_key:
             idx = self.model().index(row_count, 0)
             set_current = QPersistentModelIndex(idx)
     self.set_current_idx(set_current)
     run_hook('update_contacts_tab', self)
Exemplo n.º 23
0
 def _sign_tx(self, tx, password, on_success, on_failure):
     try:
         self.wallet.sign_transaction(tx, password)
     except InvalidPassword:
         Clock.schedule_once(lambda dt: on_failure(_("Invalid PIN")))
         return
     on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success,
                           on_failure) or on_success
     Clock.schedule_once(lambda dt: on_success(tx))
Exemplo n.º 24
0
    def __init__(self, config: 'SimpleConfig', daemon: 'Daemon',
                 plugins: 'Plugins'):
        set_language(config.get('language', get_default_language()))
        Logger.__init__(self)
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum.desktop')
        self.gui_thread = threading.current_thread()
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        self.windows = []
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum.png"))
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.network_dialog = None
        self.lightning_dialog = None
        self.watchtower_dialog = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        # init tray
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()
        self.app.new_window_signal.connect(self.start_new_window)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)
Exemplo n.º 25
0
    def __init__(self, config: 'SimpleConfig', daemon: 'Daemon',
                 plugins: 'Plugins'):
        set_language(config.get('language', self.get_default_language()))
        Logger.__init__(self)
        #os.environ['QML_IMPORT_TRACE'] = '1'
        #os.environ['QT_DEBUG_PLUGINS'] = '1'

        self.logger.info(
            f"Qml GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}"
        )
        self.logger.info("CWD=%s" % os.getcwd())
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum.desktop')
        if hasattr(QtCore.Qt, "AA_EnableHighDpiScaling"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_EnableHighDpiScaling)

        if not "QT_QUICK_CONTROLS_STYLE" in os.environ:
            os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"

        self.gui_thread = threading.current_thread()
        self.plugins = plugins
        self.app = ElectrumQmlApplication(sys.argv, config, daemon)
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec
        self.timer.timeout.connect(
            lambda: None)  # periodically enter python scope

        sys.excepthook = self.excepthook
        threading.excepthook = self.texcepthook

        # Initialize any QML plugins
        run_hook('init_qml', self)
        self.app.engine.load('electrum/gui/qml/components/main.qml')
Exemplo n.º 26
0
 def on_start(self):
     ''' This is the start point of the kivy ui
     '''
     import time
     Logger.info('Time to on_start: {} <<<<<<<<'.format(time.process_time()))
     Window.bind(size=self.on_size, on_keyboard=self.on_keyboard)
     Window.bind(on_key_down=self.on_key_down)
     #Window.softinput_mode = 'below_target'
     self.on_size(Window, Window.size)
     self.init_ui()
     crash_reporter.ExceptionHook(self)
     # init plugins
     run_hook('init_kivy', self)
     # fiat currency
     self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''
     # default tab
     self.switch_to('history')
     # bind intent for bitcoin: URI scheme
     if platform == 'android':
         from android import activity
         from jnius import autoclass
         PythonActivity = autoclass('org.kivy.android.PythonActivity')
         mactivity = PythonActivity.mActivity
         self.on_new_intent(mactivity.getIntent())
         activity.bind(on_new_intent=self.on_new_intent)
     # connect callbacks
     if self.network:
         interests = ['wallet_updated', 'network_updated', 'blockchain_updated',
                      'status', 'new_transaction', 'verified']
         self.network.register_callback(self.on_network_event, interests)
         self.network.register_callback(self.on_fee, ['fee'])
         self.network.register_callback(self.on_fee_histogram, ['fee_histogram'])
         self.network.register_callback(self.on_quotes, ['on_quotes'])
         self.network.register_callback(self.on_history, ['on_history'])
         self.network.register_callback(self.on_payment_received, ['payment_received'])
         self.network.register_callback(self.on_channels, ['channels'])
         self.network.register_callback(self.on_channel, ['channel'])
         self.network.register_callback(self.on_payment_status, ['payment_status'])
     # load wallet
     self.load_wallet_by_name(self.electrum_config.get_wallet_path(use_gui_last_wallet=True))
     # URI passed in config
     uri = self.electrum_config.get('url')
     if uri:
         self.set_URI(uri)
Exemplo n.º 27
0
    def create_menu(self, position):
        menu = QMenu()
        idx = self.indexAt(position)
        column = idx.column() or self.Columns.NAME
        selected_keys = []
        for s_idx in self.selected_in_column(self.Columns.NAME):
            sel_key = self.model().itemFromIndex(s_idx).data(Qt.UserRole)
            selected_keys.append(sel_key)
        if not selected_keys or not idx.isValid():
            menu.addAction(_("New contact"),
                           lambda: self.parent.new_contact_dialog())
            menu.addAction(_("Import file"), lambda: self.import_contacts())
            menu.addAction(_("Export file"), lambda: self.export_contacts())
        else:
            column_title = self.model().horizontalHeaderItem(column).text()
            column_data = '\n'.join(
                self.model().itemFromIndex(s_idx).text()
                for s_idx in self.selected_in_column(column))
            menu.addAction(
                _("Copy {}").format(column_title),
                lambda: self.place_text_on_clipboard(column_data,
                                                     title=column_title))
            if column in self.editable_columns:
                item = self.model().itemFromIndex(idx)
                if item.isEditable():
                    # would not be editable if openalias
                    persistent = QPersistentModelIndex(idx)
                    menu.addAction(
                        _("Edit {}").format(column_title),
                        lambda p=persistent: self.edit(QModelIndex(p)))
            menu.addAction(_("Pay to"),
                           lambda: self.parent.payto_contacts(selected_keys))
            menu.addAction(_("Delete"),
                           lambda: self.parent.delete_contacts(selected_keys))
            URLs = [
                block_explorer_URL(self.config, 'addr', key)
                for key in filter(is_address, selected_keys)
            ]
            if URLs:
                menu.addAction(_("View on block explorer"),
                               lambda: [webopen(u) for u in URLs])

        run_hook('create_contact_menu', menu, selected_keys)
        menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 28
0
 def create_menu(self, position):
     item = self.itemAt(position)
     if not item:
         return
     addr = str(item.text(1))
     req = self.wallet.receive_requests.get(addr)
     if req is None:
         self.update()
         return
     column = self.currentColumn()
     column_title = self.headerItem().text(column)
     column_data = item.text(column)
     menu = QMenu(self)
     menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
     menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))
     menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
     menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
     run_hook('receive_list_menu', menu, addr)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 29
0
 def create_menu(self, position):
     items = self.selected_in_column(0)
     if len(items) > 1:
         keys = [item.data(ROLE_KEY) for item in items]
         menu = QMenu(self)
         menu.addAction(_("Delete requests"),
                        lambda: self.parent.delete_requests(keys))
         menu.exec_(self.viewport().mapToGlobal(position))
         return
     idx = self.indexAt(position)
     item = self.model().itemFromIndex(idx)
     # TODO use siblingAtColumn when min Qt version is >=5.11
     item = self.model().itemFromIndex(
         idx.sibling(idx.row(), self.Columns.DATE))
     if not item:
         return
     key = item.data(ROLE_KEY)
     request_type = item.data(ROLE_REQUEST_TYPE)
     req = self.wallet.get_request(key)
     if req is None:
         self.update()
         return
     menu = QMenu(self)
     self.add_copy_menu(menu, idx)
     if request_type == PR_TYPE_LN:
         menu.addAction(
             _("Copy Request"),
             lambda: self.parent.do_copy(req['invoice'],
                                         title='Lightning Request'))
     else:
         menu.addAction(
             _("Copy Request"),
             lambda: self.parent.do_copy(req['URI'], title='Bitcoin URI'))
         menu.addAction(
             _("Copy Address"),
             lambda: self.parent.do_copy(req['address'],
                                         title='Bitcoin Address'))
     if 'view_url' in req:
         menu.addAction(_("View in web browser"),
                        lambda: webopen(req['view_url']))
     menu.addAction(_("Delete"), lambda: self.parent.delete_requests([key]))
     run_hook('receive_list_menu', menu, key)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 30
0
 def get_max_amount(self):
     if run_hook('abort_send', self):
         return ''
     inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
     if not inputs:
         return ''
     addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
     outputs = [(TYPE_ADDRESS, addr, '!')]
     try:
         tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
     except NoDynamicFeeEstimates as e:
         Clock.schedule_once(lambda dt, bound_e=e: self.show_error(str(bound_e)))
         return ''
     except NotEnoughFunds:
         return ''
     amount = tx.output_value()
     __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
     amount_after_all_fees = amount - x_fee_amount
     return format_satoshis_plain(amount_after_all_fees, self.decimal_point())
Exemplo n.º 31
0
 def load_wallet(self, wallet: 'Abstract_Wallet'):
     if self.wallet:
         self.stop_wallet()
     self.wallet = wallet
     self.wallet_name = wallet.basename()
     self.update_wallet()
     # Once GUI has been initialized check if we want to announce something
     # since the callback has been called before the GUI was initialized
     if self.receive_screen:
         self.receive_screen.clear()
     self.update_tabs()
     run_hook('load_wallet', wallet, self)
     try:
         wallet.try_detecting_internal_addresses_corruption()
     except InternalAddressCorruption as e:
         self.show_error(str(e))
         send_exception_to_crash_reporter(e)
         return
     self.use_change = self.wallet.use_change
Exemplo n.º 32
0
 def create_menu(self, position):
     idx = self.indexAt(position)
     item = self.model().itemFromIndex(idx)
     # TODO use siblingAtColumn when min Qt version is >=5.11
     item = self.model().itemFromIndex(
         idx.sibling(idx.row(), self.Columns.DATE))
     if not item:
         return
     key = item.data(ROLE_KEY)
     request_type = item.data(ROLE_REQUEST_TYPE)
     req = self.wallet.get_request(key)
     if req is None:
         self.update()
         return
     column = idx.column()
     column_title = self.model().horizontalHeaderItem(column).text()
     column_data = self.model().itemFromIndex(idx).text()
     menu = QMenu(self)
     if column == self.Columns.AMOUNT:
         column_data = column_data.strip()
     menu.addAction(
         _("Copy {}").format(column_title),
         lambda: self.parent.do_copy(column_title, column_data))
     if request_type == PR_TYPE_ADDRESS:
         menu.addAction(_("Copy Address"),
                        lambda: self.parent.do_copy('Address', key))
     if request_type == PR_TYPE_LN:
         menu.addAction(
             _("Copy lightning payment request"),
             lambda: self.parent.do_copy('Request', req['invoice']))
     else:
         menu.addAction(_("Copy URI"),
                        lambda: self.parent.do_copy('URI', req['URI']))
     if 'http_url' in req:
         menu.addAction(_("View in web browser"),
                        lambda: webopen(req['http_url']))
     # do bip70 only for browser access
     # so, each request should have an ID, regardless
     #menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
     menu.addAction(_("Delete"), lambda: self.parent.delete_request(key))
     run_hook('receive_list_menu', menu, key)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 33
0
 def update(self):
     current_key = self.current_item_user_role(col=self.Columns.NAME)
     self.model().clear()
     self.update_headers(self.__class__.headers)
     set_current = None
     for key in sorted(self.parent.contacts.keys()):
         contact_type, name = self.parent.contacts[key]
         items = [QStandardItem(x) for x in (name, key)]
         items[self.Columns.NAME].setEditable(contact_type != 'openalias')
         items[self.Columns.ADDRESS].setEditable(False)
         items[self.Columns.NAME].setData(key, Qt.UserRole)
         row_count = self.model().rowCount()
         self.model().insertRow(row_count, items)
         if key == current_key:
             idx = self.model().index(row_count, self.Columns.NAME)
             set_current = QPersistentModelIndex(idx)
     self.set_current_idx(set_current)
     # FIXME refresh loses sort order; so set "default" here:
     self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder)
     run_hook('update_contacts_tab', self)
Exemplo n.º 34
0
 def get_max_amount(self):
     from electrum.transaction import TxOutput
     if run_hook('abort_send', self):
         return ''
     inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
     if not inputs:
         return ''
     addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
     outputs = [TxOutput(TYPE_ADDRESS, addr, '!')]
     try:
         tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
     except NoDynamicFeeEstimates as e:
         Clock.schedule_once(lambda dt, bound_e=e: self.show_error(str(bound_e)))
         return ''
     except NotEnoughFunds:
         return ''
     amount = tx.output_value()
     __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
     amount_after_all_fees = amount - x_fee_amount
     return format_satoshis_plain(amount_after_all_fees, self.decimal_point())
Exemplo n.º 35
0
    def update(self):
        tx = self.tx
        self._update_amount_label()

        if self.not_enough_funds:
            text = _("Not enough funds")
            c, u, x = self.wallet.get_frozen_balance()
            if c+u+x:
                text += " ({} {} {})".format(
                    self.main_window.format_amount(c + u + x).strip(), self.main_window.base_unit(), _("are frozen")
                )
            self.toggle_send_button(False, message=text)
            return

        if not tx:
            return

        fee = tx.get_fee()
        fee_display = tx.get_fee_display()
        name_fee = None if (fee is None or fee_display is None) else (fee_display - fee)
        self.fee_label.setText(self.main_window.format_amount_and_units(fee))
        if name_fee is not None and name_fee != 0:
            self.name_fee_label.setVisible(True)
            self.name_fee_value.setVisible(True)
            self.name_fee_value.setText(self.main_window.format_amount_and_units(name_fee))
        else:
            self.name_fee_label.setVisible(False)
            self.name_fee_value.setVisible(False)
        x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            self.extra_fee_label.setVisible(True)
            self.extra_fee_value.setVisible(True)
            self.extra_fee_value.setText(self.main_window.format_amount_and_units(x_fee_amount))

        amount = tx.output_value() if self.output_value == '!' else self.output_value
        feerate = Decimal(fee) / tx.estimated_size()  # sat/byte
        fee_ratio = Decimal(fee) / amount if amount else 1
        if feerate < self.wallet.relayfee() / 1000:
            msg = '\n'.join([
                _("This transaction requires a higher fee, or it will not be propagated by your current server"),
                _("Try to raise your transaction fee, or use a server with a lower relay fee.")
            ])
            self.toggle_send_button(False, message=msg)
        elif fee_ratio >= FEE_RATIO_HIGH_WARNING:
            self.toggle_send_button(True,
                                    message=_('Warning') + ': ' + _("The fee for this transaction seems unusually high.")
                                            + f'\n({fee_ratio*100:.2f}% of amount)')
        elif feerate > FEERATE_WARNING_HIGH_FEE / 1000:
            self.toggle_send_button(True,
                                    message=_('Warning') + ': ' + _("The fee for this transaction seems unusually high.")
                                            + f'\n(feerate: {feerate:.2f} swartz/byte)')
        else:
            self.toggle_send_button(True)
Exemplo n.º 36
0
 def update(self):
     current_key = self.current_item_user_role(col=self.Columns.NAME)
     self.model().clear()
     self.update_headers(self.__class__.headers)
     set_current = None
     for key in sorted(self.parent.contacts.keys()):
         contact_type, name = self.parent.contacts[key]
         items = [QStandardItem(x) for x in (name, key)]
         items[self.Columns.NAME].setEditable(contact_type != 'openalias')
         items[self.Columns.ADDRESS].setEditable(False)
         items[self.Columns.NAME].setData(key, Qt.UserRole)
         row_count = self.model().rowCount()
         self.model().insertRow(row_count, items)
         if key == current_key:
             idx = self.model().index(row_count, self.Columns.NAME)
             set_current = QPersistentModelIndex(idx)
     self.set_current_idx(set_current)
     # FIXME refresh loses sort order; so set "default" here:
     self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder)
     run_hook('update_contacts_tab', self)
Exemplo n.º 37
0
 def on_start(self):
     ''' This is the start point of the kivy ui
     '''
     import time
     Logger.info('Time to on_start: {} <<<<<<<<'.format(time.clock()))
     win = Window
     win.bind(size=self.on_size, on_keyboard=self.on_keyboard)
     win.bind(on_key_down=self.on_key_down)
     #win.softinput_mode = 'below_target'
     self.on_size(win, win.size)
     self.init_ui()
     crash_reporter.ExceptionHook(self)
     # init plugins
     run_hook('init_kivy', self)
     # fiat currency
     self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''
     # default tab
     self.switch_to('history')
     # bind intent for bitcoin: URI scheme
     if platform == 'android':
         from android import activity
         from jnius import autoclass
         PythonActivity = autoclass('org.kivy.android.PythonActivity')
         mactivity = PythonActivity.mActivity
         self.on_new_intent(mactivity.getIntent())
         activity.bind(on_new_intent=self.on_new_intent)
     # connect callbacks
     if self.network:
         interests = ['wallet_updated', 'network_updated', 'blockchain_updated',
                      'status', 'new_transaction', 'verified']
         self.network.register_callback(self.on_network_event, interests)
         self.network.register_callback(self.on_fee, ['fee'])
         self.network.register_callback(self.on_fee_histogram, ['fee_histogram'])
         self.network.register_callback(self.on_quotes, ['on_quotes'])
         self.network.register_callback(self.on_history, ['on_history'])
     # load wallet
     self.load_wallet_by_name(self.electrum_config.get_wallet_path())
     # URI passed in config
     uri = self.electrum_config.get('url')
     if uri:
         self.set_URI(uri)
Exemplo n.º 38
0
 def create_menu(self, position):
     items = self.selected_in_column(0)
     if len(items) > 1:
         keys = [item.data(ROLE_KEY) for item in items]
         menu = QMenu(self)
         menu.addAction(_("Delete requests"),
                        lambda: self.parent.delete_requests(keys))
         menu.exec_(self.viewport().mapToGlobal(position))
         return
     idx = self.indexAt(position)
     # TODO use siblingAtColumn when min Qt version is >=5.11
     item = self.item_from_index(idx.sibling(idx.row(), self.Columns.DATE))
     if not item:
         return
     key = item.data(ROLE_KEY)
     req = self.wallet.get_request(key)
     if req is None:
         self.update()
         return
     menu = QMenu(self)
     self.add_copy_menu(menu, idx)
     if req.is_lightning():
         menu.addAction(
             _("Copy Request"),
             lambda: self.parent.do_copy(req.invoice,
                                         title='Lightning Request'))
     else:
         URI = self.wallet.get_request_URI(req)
         menu.addAction(
             _("Copy Request"), lambda: self.parent.do_copy(
                 URI, title='{name} URI'.format(name=constants.net.NAME)))
         menu.addAction(
             _("Copy Address"), lambda: self.parent.do_copy(
                 req.get_address(),
                 title='{name} Address'.format(name=constants.net.NAME)))
     #if 'view_url' in req:
     #    menu.addAction(_("View in web browser"), lambda: webopen(req['view_url']))
     menu.addAction(_("Delete"), lambda: self.parent.delete_requests([key]))
     run_hook('receive_list_menu', menu, key)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 39
0
    def _do_pay_onchain(self, invoice, rbf):
        # make unsigned transaction
        outputs = invoice.outputs  # type: List[PartialTxOutput]
        coins = self.app.wallet.get_spendable_coins(None)
        try:
            tx = self.app.wallet.make_unsigned_transaction(coins=coins,
                                                           outputs=outputs)
        except NotEnoughFunds:
            self.app.show_error(_("Not enough funds"))
            return
        except Exception as e:
            Logger.exception('')
            self.app.show_error(repr(e))
            return
        if rbf:
            tx.set_rbf(True)
        fee = tx.get_fee()
        fee_display = tx.get_fee_display()
        name_fee = None if (fee is None
                            or fee_display is None) else (fee_display - fee)
        amount = sum(map(lambda x: x.value_display, outputs)) if '!' not in [
            x.value_display for x in outputs
        ] else tx.output_value_display()
        msg = [
            _("Amount to be sent") + ": " +
            self.app.format_amount_and_units(amount),
            _("Mining fee") + ": " + self.app.format_amount_and_units(fee),
        ]
        if name_fee is not None and name_fee != 0:
            msg.append(
                _("Name registration fee") + ": " +
                self.app.format_amount_and_units(name_fee))
        x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            msg.append(
                _("Additional fees") + ": " +
                self.app.format_amount_and_units(x_fee_amount))

        feerate = Decimal(fee) / tx.estimated_size()  # sat/byte
        fee_ratio = Decimal(fee) / amount if amount else 1
        if fee_ratio >= FEE_RATIO_HIGH_WARNING:
            msg.append(
                _('Warning') + ': ' +
                _("The fee for this transaction seems unusually high.") +
                f' ({fee_ratio*100:.2f}% of amount)')
        elif feerate > FEERATE_WARNING_HIGH_FEE / 1000:
            msg.append(
                _('Warning') + ': ' +
                _("The fee for this transaction seems unusually high.") +
                f' (feerate: {feerate:.2f} swartz/byte)')
        self.app.protected('\n'.join(msg), self.send_tx, (tx, ))
Exemplo n.º 40
0
 def create_menu(self, position):
     item = self.itemAt(position)
     if not item:
         return
     addr = str(item.text(1))
     req = self.wallet.receive_requests[addr]
     column = self.currentColumn()
     column_title = self.headerItem().text(column)
     column_data = item.text(column)
     menu = QMenu(self)
     menu.addAction(
         _("Copy %s") % column_title,
         lambda: self.parent.app.clipboard().setText(column_data))
     menu.addAction(
         _("Copy URI"), lambda: self.parent.view_and_paste(
             'URI', '', self.parent.get_request_URI(addr)))
     menu.addAction(_("Save as BIP70 file"),
                    lambda: self.parent.export_payment_request(addr))
     menu.addAction(_("Delete"),
                    lambda: self.parent.delete_payment_request(addr))
     run_hook('receive_list_menu', menu, addr)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 41
0
 def create_menu(self, position):
     idx = self.indexAt(position)
     # TODO use siblingAtColumn when min Qt version is >=5.11
     item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.ADDRESS))
     if not item:
         return
     addr = item.text()
     req = self.wallet.receive_requests.get(addr)
     if req is None:
         self.update()
         return
     column = idx.column()
     column_title = self.model().horizontalHeaderItem(column).text()
     column_data = item.text()
     menu = QMenu(self)
     if column != self.Columns.SIGNATURE:
         menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
     menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))
     menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
     menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
     run_hook('receive_list_menu', menu, addr)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 42
0
 def create_menu(self, position):
     menu = QMenu()
     selected = self.selectedItems()
     multi_select = len(selected) > 1
     if not selected:
         pass
     elif not multi_select:
         item = selected[0]
         txid = item.data(0, Qt.UserRole)
         column = self.currentColumn()
         column_title = self.headerItem().text(column)
         column_data = '\n'.join([item.text(column) for item in selected])
         menu.addAction(
             _("Copy %s") % column_title,
             lambda: self.parent.app.clipboard().setText(column_data))
         menu.addAction(_("Copy Transaction ID"),
                        lambda: self.parent.app.clipboard().setText(txid))
         URL = block_explorer_URL(self.config, {'tx': txid})
         if URL:
             menu.addAction(_("View on block explorer"),
                            lambda: open_browser(URL))
     run_hook('create_token_hist_menu', menu, selected)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 43
0
 def create_menu(self, position):
     idx = self.indexAt(position)
     item = self.model().itemFromIndex(idx)
     # TODO use siblingAtColumn when min Qt version is >=5.11
     item = self.model().itemFromIndex(
         idx.sibling(idx.row(), self.Columns.DATE))
     if not item:
         return
     key = item.data(ROLE_KEY)
     request_type = item.data(ROLE_REQUEST_TYPE)
     req = self.wallet.get_request(key)
     if req is None:
         self.update()
         return
     column = idx.column()
     column_title = self.model().horizontalHeaderItem(column).text()
     column_data = self.model().itemFromIndex(idx).text()
     menu = QMenu(self)
     if column == self.Columns.AMOUNT:
         column_data = column_data.strip()
     menu.addAction(
         _("Copy {}").format(column_title),
         lambda: self.parent.do_copy(column_title, column_data))
     if request_type == PR_TYPE_LN:
         menu.addAction(
             _("Copy Request"), lambda: self.parent.do_copy(
                 'Lightning Request', req['invoice']))
     else:
         menu.addAction(
             _("Copy Request"),
             lambda: self.parent.do_copy('Bitcoin URI', req['URI']))
     if 'view_url' in req:
         menu.addAction(_("View in web browser"),
                        lambda: webopen(req['view_url']))
     menu.addAction(_("Delete"), lambda: self.parent.delete_request(key))
     run_hook('receive_list_menu', menu, key)
     menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 44
0
    def __init__(
            self, text="", allow_multi: bool = False,
            *,
            config: SimpleConfig,
            setText: Callable[[str], None] = None,
    ):
        ButtonsTextEdit.__init__(self, text)
        self.setReadOnly(False)

        input_qr_from_camera = partial(
            self.input_qr_from_camera,
            config=config,
            allow_multi=allow_multi,
            show_error=self.show_error,
            setText=setText,
        )
        self.on_qr_from_camera_input_btn = input_qr_from_camera

        input_qr_from_screenshot = partial(
            self.input_qr_from_screenshot,
            allow_multi=allow_multi,
            show_error=self.show_error,
            setText=setText,
        )
        self.on_qr_from_screenshot_input_btn = input_qr_from_screenshot

        input_file = partial(self.input_file, config=config, show_error=self.show_error, setText=setText)

        self.add_menu_button(
            options=[
                ("picture_in_picture.png", _("Read QR code from screen"), input_qr_from_screenshot),
                ("file.png",               _("Read file"),                input_file),
            ],
        )
        self.add_qr_input_from_camera_button(config=config, show_error=self.show_error, allow_multi=allow_multi, setText=setText)

        run_hook('scan_text_edit', self)
Exemplo n.º 45
0
    def update(self):
        tx = self.tx
        amount = tx.output_value_display() if self.output_value == '!' else self.output_value
        self.amount_label.setText(self.main_window.format_amount_and_units(amount))

        if self.not_enough_funds:
            text = _("Not enough funds")
            c, u, x = self.wallet.get_frozen_balance()
            if c+u+x:
                text += " ({} {} {})".format(
                    self.main_window.format_amount(c + u + x).strip(), self.main_window.base_unit(), _("are frozen")
                )
            self.disable(text)
            return

        if not tx:
            return

        fee = tx.get_fee()
        fee_display = tx.get_fee_display()
        name_fee = None if (fee is None or fee_display is None) else (fee_display - fee)
        self.fee_label.setText(self.main_window.format_amount_and_units(fee))
        if name_fee is not None and name_fee != 0:
            self.name_fee_label.setVisible(True)
            self.name_fee_value.setVisible(True)
            self.name_fee_value.setText(self.main_window.format_amount_and_units(name_fee))
        else:
            self.name_fee_label.setVisible(False)
            self.name_fee_value.setVisible(False)
        x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            self.extra_fee_label.setVisible(True)
            self.extra_fee_value.setVisible(True)
            self.extra_fee_value.setText(self.main_window.format_amount_and_units(x_fee_amount))

        feerate_warning = FEERATE_WARNING_HIGH_FEE
        low_fee = fee < self.wallet.relayfee() * tx.estimated_size() / 1000
        high_fee = fee > feerate_warning * tx.estimated_size() / 1000
        if low_fee:
            msg = '\n'.join([
                _("This transaction requires a higher fee, or it will not be propagated by your current server"),
                _("Try to raise your transaction fee, or use a server with a lower relay fee.")
            ])
            self.disable(msg)
        elif high_fee:
            self.disable(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
        else:
            self.enable()
Exemplo n.º 46
0
    def update(self):
        tx = self.tx
        self._update_amount_label()

        if self.not_enough_funds:
            text = self.main_window.get_text_not_enough_funds_mentioning_frozen(
            )
            self.toggle_send_button(False, message=text)
            return

        if not tx:
            return

        fee = tx.get_fee()
        self.fee_label.setText(self.main_window.format_amount_and_units(fee))
        x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            self.extra_fee_label.setVisible(True)
            self.extra_fee_value.setVisible(True)
            self.extra_fee_value.setText(
                self.main_window.format_amount_and_units(x_fee_amount))

        amount = tx.output_value(
        ) if self.output_value == '!' else self.output_value
        feerate = Decimal(fee) / tx.estimated_size()  # sat/byte
        fee_ratio = Decimal(fee) / amount if amount else 1
        if feerate < self.wallet.relayfee() / 1000:
            msg = '\n'.join([
                _("This transaction requires a higher fee, or it will not be propagated by your current server"
                  ),
                _("Try to raise your transaction fee, or use a server with a lower relay fee."
                  )
            ])
            self.toggle_send_button(False, message=msg)
        elif fee_ratio >= FEE_RATIO_HIGH_WARNING:
            self.toggle_send_button(
                True,
                message=_('Warning') + ': ' +
                _("The fee for this transaction seems unusually high.") +
                f'\n({fee_ratio*100:.2f}% of amount)')
        elif feerate > FEERATE_WARNING_HIGH_FEE / 1000:
            self.toggle_send_button(
                True,
                message=_('Warning') + ': ' +
                _("The fee for this transaction seems unusually high.") +
                f'\n(feerate: {feerate:.2f} sat/byte)')
        else:
            self.toggle_send_button(True)
Exemplo n.º 47
0
    def create_menu(self, position):
        menu = QMenu()
        selected = self.selectedItems()
        if not selected:
            menu.addAction(_("New contact"),
                           lambda: self.parent.new_contact_dialog())
            menu.addAction(_("Import file"), lambda: self.import_contacts())
            menu.addAction(_("Export file"), lambda: self.export_contacts())
        else:
            names = [item.text(0) for item in selected]
            keys = [item.text(1) for item in selected]
            column = self.currentColumn()
            column_title = self.headerItem().text(column)
            column_data = '\n'.join([item.text(column) for item in selected])
            menu.addAction(
                _("Copy {}").format(column_title),
                lambda: self.parent.app.clipboard().setText(column_data))
            if column in self.editable_columns:
                item = self.currentItem()
                menu.addAction(
                    _("Edit {}").format(column_title),
                    lambda: self.editItem(item, column))
            menu.addAction(_("Pay to"),
                           lambda: self.parent.payto_contacts(keys))
            menu.addAction(_("Delete"),
                           lambda: self.parent.delete_contacts(keys))
            URLs = [
                block_explorer_URL(self.config, 'addr', key)
                for key in filter(is_address, keys)
            ]
            if URLs:
                menu.addAction(_("View on block explorer"),
                               lambda: map(webbrowser.open, URLs))

        run_hook('create_contact_menu', menu, selected)
        menu.exec_(self.viewport().mapToGlobal(position))
Exemplo n.º 48
0
    def _do_pay_onchain(self, invoice, rbf):
        # make unsigned transaction
        outputs = invoice['outputs']  # type: List[PartialTxOutput]
        amount = sum(map(lambda x: x.value_display, outputs))
        coins = self.app.wallet.get_spendable_coins(None)
        try:
            tx = self.app.wallet.make_unsigned_transaction(coins=coins,
                                                           outputs=outputs)
        except NotEnoughFunds:
            self.app.show_error(_("Not enough funds"))
            return
        except Exception as e:
            Logger.exception('')
            self.app.show_error(repr(e))
            return
        if rbf:
            tx.set_rbf(True)
        fee = tx.get_fee()
        fee_display = tx.get_fee_display()
        name_fee = None if (fee is None
                            or fee_display is None) else (fee_display - fee)
        msg = [
            _("Amount to be sent") + ": " +
            self.app.format_amount_and_units(amount),
            _("Mining fee") + ": " + self.app.format_amount_and_units(fee),
        ]
        if name_fee is not None and name_fee != 0:
            msg.append(
                _("Name registration fee") + ": " +
                self.app.format_amount_and_units(name_fee))
        x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            msg.append(
                _("Additional fees") + ": " +
                self.app.format_amount_and_units(x_fee_amount))

        feerate_warning = simple_config.FEERATE_WARNING_HIGH_FEE
        if fee > feerate_warning * tx.estimated_size() / 1000:
            msg.append(
                _('Warning') + ': ' +
                _("The fee for this transaction seems unusually high."))
        msg.append(_("Enter your PIN code to proceed"))
        self.app.protected('\n'.join(msg), self.send_tx, (tx, ))
Exemplo n.º 49
0
 def update_tx(self):
     outputs = self.invoice.outputs
     try:
         # make unsigned transaction
         coins = self.app.wallet.get_spendable_coins(None)
         tx = self.app.wallet.make_unsigned_transaction(coins=coins,
                                                        outputs=outputs)
     except NotEnoughFunds:
         self.warning = _("Not enough funds")
         return
     except Exception as e:
         self.logger.exception('')
         self.app.show_error(repr(e))
         return
     rbf = not bool(self.ids.final_cb.active) if self.show_final else False
     tx.set_rbf(rbf)
     amount = sum(map(lambda x: x.value, outputs)) if '!' not in [
         x.value for x in outputs
     ] else tx.output_value()
     fee = tx.get_fee()
     feerate = Decimal(fee) / tx.estimated_size()  # sat/byte
     self.ids.fee_label.text = self.app.format_amount_and_units(
         fee) + f' ({feerate:.1f} sat/B)'
     self.ids.amount_label.text = self.app.format_amount_and_units(amount)
     x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
     if x_fee:
         x_fee_address, x_fee_amount = x_fee
         self.extra_fee = self.app.format_amount_and_units(x_fee_amount)
     else:
         self.extra_fee = ''
     fee_ratio = Decimal(fee) / amount if amount else 1
     if fee_ratio >= FEE_RATIO_HIGH_WARNING:
         self.warning = _('Warning') + ': ' + _(
             "The fee for this transaction seems unusually high."
         ) + f' ({fee_ratio*100:.2f}% of amount)'
     elif feerate > FEERATE_WARNING_HIGH_FEE / 1000:
         self.warning = _('Warning') + ': ' + _(
             "The fee for this transaction seems unusually high."
         ) + f' (feerate: {feerate:.2f} sat/byte)'
     else:
         self.warning = ''
     self.tx = tx
Exemplo n.º 50
0
    def _do_send(self, amount, desc, outputs, gas_fee):
        from electrum.plugin import run_hook
        from electrum import simple_config
        # make unsigned transaction
        config = self.app.electrum_config
        coins = self.app.wallet.get_spendable_coins(None, config)
        sender = self.bind_addr
        try:
            tx = self.app.wallet.make_unsigned_transaction(coins,
                                                           outputs,
                                                           config,
                                                           None,
                                                           change_addr=sender,
                                                           gas_fee=gas_fee,
                                                           sender=sender)
        except NotEnoughFunds:
            self.app.show_error(_("Insufficient funds"))
            return
        except Exception as e:
            self.app.show_error(str(e))
            return
        fee = tx.get_fee()
        msg = [
            _(desc),
            _("Mining fee") + ": " +
            self.app.format_amount_and_units(fee - gas_fee),
            _("Gas fee") + ": " + self.app.format_amount_and_units(gas_fee),
        ]
        x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            msg.append(
                _("Additional fees") + ": " +
                self.app.format_amount_and_units(x_fee_amount))

        feerate_warning = simple_config.FEERATE_WARNING_HIGH_FEE
        if fee > feerate_warning * tx.estimated_size() / 1000:
            msg.append(
                _('Warning') + ': ' +
                _("The fee for this transaction seems unusually high."))
        msg.append(_("Enter your PIN code to proceed"))
        self.app.protected('\n'.join(msg), self.send_tx, (tx, desc))
Exemplo n.º 51
0
 def update_tx(self):
     rbf = not bool(self.ids.final_cb.active) if self.show_final else False
     try:
         # make unsigned transaction
         tx = self.make_tx(rbf)
     except NotEnoughFunds:
         self.warning = _("Not enough funds")
         self.ids.ok_button.disabled = True
         return
     except Exception as e:
         self.ids.ok_button.disabled = True
         self.app.logger.exception('')
         self.app.show_error(repr(e))
         return
     self.ids.ok_button.disabled = False
     amount = self.amount if (self.amount.rvn_value != '!' and all(
         val != '!'
         for val in self.amount.assets.values())) else tx.output_value()
     tx_size = tx.estimated_size()
     fee = tx.get_fee()
     self.ids.fee_label.text = self.app.format_amount_and_units(fee)
     feerate = Decimal(fee) / tx_size  # sat/byte
     self.ids.feerate_label.text = f'{feerate:.1f} sat/B'
     self.ids.amount_label.text = self.app.format_amount_and_units(amount)
     x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
     if x_fee:
         x_fee_address, x_fee_amount = x_fee
         self.extra_fee = self.app.format_amount_and_units(x_fee_amount)
     else:
         self.extra_fee = ''
     fee_warning_tuple = self.app.wallet.get_tx_fee_warning(
         invoice_amt=amount, tx_size=tx_size, fee=fee)
     if fee_warning_tuple:
         allow_send, long_warning, short_warning = fee_warning_tuple
         self.warning = long_warning
     else:
         self.warning = ''
     self.tx = tx
Exemplo n.º 52
0
    def _do_send(self, amount, message, outputs, rbf, flodata):
        # make unsigned transaction
        config = self.app.electrum_config
        coins = self.app.wallet.get_spendable_coins(None, config)
        try:
            tx = self.app.wallet.make_unsigned_transaction(coins,
                                                           outputs,
                                                           config,
                                                           None,
                                                           flodata=flodata)
        except NotEnoughFunds:
            self.app.show_error(_("Not enough funds"))
            return
        except Exception as e:
            traceback.print_exc(file=sys.stdout)
            self.app.show_error(str(e))
            return
        if rbf:
            tx.set_rbf(True)
        fee = tx.get_fee()
        msg = [
            _("Amount to be sent") + ": " +
            self.app.format_amount_and_units(amount),
            _("Mining fee") + ": " + self.app.format_amount_and_units(fee),
        ]
        x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
        if x_fee:
            x_fee_address, x_fee_amount = x_fee
            msg.append(
                _("Additional fees") + ": " +
                self.app.format_amount_and_units(x_fee_amount))

        if fee >= config.get('confirm_fee', 100000):
            msg.append(
                _('Warning') + ': ' +
                _("The fee for this transaction seems unusually high."))
        msg.append(_("Enter your PIN code to proceed"))
        self.app.protected('\n'.join(msg), self.send_tx, (tx, message))
Exemplo n.º 53
0
    def update(self):
        if not self.finalized:
            self.update_fee_fields()
            self.finalize_button.setEnabled(self.tx is not None)
        if self.tx is None:
            return
        self.update_io()
        desc = self.desc
        base_unit = self.main_window.base_unit()
        format_amount = self.main_window.format_amount
        tx_details = self.wallet.get_tx_info(self.tx)
        tx_mined_status = tx_details.tx_mined_status
        exp_n = tx_details.mempool_depth_bytes
        amount, fee = tx_details.amount, tx_details.fee
        size = self.tx.estimated_size()
        txid = self.tx.txid()
        lnworker_history = self.wallet.lnworker.get_onchain_history(
        ) if self.wallet.lnworker else {}
        if txid in lnworker_history:
            item = lnworker_history[txid]
            ln_amount = item['amount_msat'] / 1000
            if amount is None:
                tx_mined_status = self.wallet.lnworker.lnwatcher.get_tx_height(
                    txid)
        else:
            ln_amount = None
        self.broadcast_button.setEnabled(tx_details.can_broadcast)
        can_sign = not self.tx.is_complete() and \
            (self.wallet.can_sign(self.tx) or bool(self.external_keypairs))
        self.sign_button.setEnabled(can_sign)
        if self.finalized and tx_details.txid:
            self.tx_hash_e.setText(tx_details.txid)
        else:
            # note: when not finalized, RBF and locktime changes do not trigger
            #       a make_tx, so the txid is unreliable, hence:
            self.tx_hash_e.setText(_('Unknown'))
        if desc is None:
            self.tx_desc.hide()
        else:
            self.tx_desc.setText(_("Description") + ': ' + desc)
            self.tx_desc.show()
        self.status_label.setText(_('Status:') + ' ' + tx_details.status)

        if tx_mined_status.timestamp:
            time_str = datetime.datetime.fromtimestamp(
                tx_mined_status.timestamp).isoformat(' ')[:-3]
            self.date_label.setText(_("Date: {}").format(time_str))
            self.date_label.show()
        elif exp_n:
            text = '%.2f MB' % (exp_n / 1000000)
            self.date_label.setText(
                _('Position in mempool: {} from tip').format(text))
            self.date_label.show()
        else:
            self.date_label.hide()
        if self.tx.locktime <= NLOCKTIME_BLOCKHEIGHT_MAX:
            locktime_final_str = f"LockTime: {self.tx.locktime} (height)"
        else:
            locktime_final_str = f"LockTime: {self.tx.locktime} ({datetime.datetime.fromtimestamp(self.tx.locktime)})"
        self.locktime_final_label.setText(locktime_final_str)
        if self.locktime_e.get_locktime() is None:
            self.locktime_e.set_locktime(self.tx.locktime)
        self.rbf_label.setText(
            _('Replace by fee') + f": {not self.tx.is_final()}")

        if tx_mined_status.header_hash:
            self.block_hash_label.setText(
                _("Included in block: {}").format(tx_mined_status.header_hash))
            self.block_height_label.setText(
                _("At block height: {}").format(tx_mined_status.height))
        else:
            self.block_hash_label.hide()
            self.block_height_label.hide()
        if amount is None and ln_amount is None:
            amount_str = _("Transaction unrelated to your wallet")
        elif amount is None:
            amount_str = ''
        elif amount > 0:
            amount_str = _("Amount received:"
                           ) + ' %s' % format_amount(amount) + ' ' + base_unit
        else:
            amount_str = _("Amount sent:"
                           ) + ' %s' % format_amount(-amount) + ' ' + base_unit
        if amount_str:
            self.amount_label.setText(amount_str)
        else:
            self.amount_label.hide()
        size_str = _("Size:") + ' %d bytes' % size
        fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit
                                       if fee is not None else _('unknown'))
        if fee is not None:
            fee_rate = fee / size * 1000
            fee_str += '  ( %s ) ' % self.main_window.format_fee_rate(fee_rate)
            feerate_warning = simple_config.FEERATE_WARNING_HIGH_FEE
            if fee_rate > feerate_warning:
                fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!'
        if isinstance(self.tx, PartialTransaction):
            risk_of_burning_coins = (
                can_sign and fee is not None
                and self.tx.is_there_risk_of_burning_coins_as_fees())
            self.fee_warning_icon.setVisible(risk_of_burning_coins)
        self.fee_label.setText(fee_str)
        self.size_label.setText(size_str)
        if ln_amount is None:
            ln_amount_str = ''
        elif ln_amount > 0:
            ln_amount_str = _(
                'Amount received in channels') + ': ' + format_amount(
                    ln_amount) + ' ' + base_unit
        elif ln_amount < 0:
            ln_amount_str = _(
                'Amount withdrawn from channels') + ': ' + format_amount(
                    -ln_amount) + ' ' + base_unit
        if ln_amount_str:
            self.ln_amount_label.setText(ln_amount_str)
        else:
            self.ln_amount_label.hide()
        show_psbt_only_widgets = self.finalized and isinstance(
            self.tx, PartialTransaction)
        for widget in self.psbt_only_widgets:
            if isinstance(widget, QMenu):
                widget.menuAction().setVisible(show_psbt_only_widgets)
            else:
                widget.setVisible(show_psbt_only_widgets)

        self.save_button.setEnabled(tx_details.can_save_as_local)
        if tx_details.can_save_as_local:
            self.save_button.setToolTip(_("Save transaction offline"))
        else:
            self.save_button.setToolTip(
                _("Transaction already saved or not yet signed."))

        run_hook('transaction_dialog_update', self)
Exemplo n.º 54
0
    def __init__(self, text=None):
        ButtonsTextEdit.__init__(self, text)
        self.setReadOnly(1)
        self.addButton(":icons/qrcode.png", self.qr_show, _("Show as QR code"))

        run_hook('show_text_edit', self)
Exemplo n.º 55
0
    def __init__(self, tx, parent, desc, prompt_if_unsaved):
        '''Transactions in the wallet will show their description.
        Pass desc to give a description for txs not yet in the wallet.
        '''
        # We want to be a top-level window
        QDialog.__init__(self, parent=None)
        # Take a copy; it might get updated in the main window by
        # e.g. the FX plugin.  If this happens during or after a long
        # sign operation the signatures are lost.
        self.tx = tx = copy.deepcopy(tx)  # type: Transaction
        try:
            self.tx.deserialize()
        except BaseException as e:
            raise SerializationError(e)
        self.main_window = parent
        self.wallet = parent.wallet
        self.prompt_if_unsaved = prompt_if_unsaved
        self.saved = False
        self.desc = desc

        # if the wallet can populate the inputs with more info, do it now.
        # as a result, e.g. we might learn an imported address tx is segwit,
        # in which case it's ok to display txid
        tx.add_inputs_info(self.wallet)

        self.setMinimumWidth(950)
        self.setWindowTitle(_("Transaction"))

        vbox = QVBoxLayout()
        self.setLayout(vbox)

        vbox.addWidget(QLabel(_("Transaction ID:")))
        self.tx_hash_e  = ButtonsLineEdit()
        qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
        qr_icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
        self.tx_hash_e.addButton(qr_icon, qr_show, _("Show as QR code"))
        self.tx_hash_e.setReadOnly(True)
        vbox.addWidget(self.tx_hash_e)
        self.tx_desc = QLabel()
        vbox.addWidget(self.tx_desc)
        self.status_label = QLabel()
        vbox.addWidget(self.status_label)
        self.date_label = QLabel()
        vbox.addWidget(self.date_label)
        self.amount_label = QLabel()
        vbox.addWidget(self.amount_label)
        self.size_label = QLabel()
        vbox.addWidget(self.size_label)
        self.fee_label = QLabel()
        vbox.addWidget(self.fee_label)

        self.add_io(vbox)

        self.sign_button = b = QPushButton(_("Sign"))
        b.clicked.connect(self.sign)

        self.broadcast_button = b = QPushButton(_("Broadcast"))
        b.clicked.connect(self.do_broadcast)

        self.save_button = b = QPushButton(_("Save"))
        save_button_disabled = not tx.is_complete()
        b.setDisabled(save_button_disabled)
        if save_button_disabled:
            b.setToolTip(SAVE_BUTTON_DISABLED_TOOLTIP)
        else:
            b.setToolTip(SAVE_BUTTON_ENABLED_TOOLTIP)
        b.clicked.connect(self.save)

        self.export_button = b = QPushButton(_("Export"))
        b.clicked.connect(self.export)

        self.cancel_button = b = QPushButton(_("Close"))
        b.clicked.connect(self.close)
        b.setDefault(True)

        self.qr_button = b = QPushButton()
        b.setIcon(read_QIcon(qr_icon))
        b.clicked.connect(self.show_qr)

        self.copy_button = CopyButton(lambda: str(self.tx), parent.app)

        # Action buttons
        self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button]
        # Transaction sharing buttons
        self.sharing_buttons = [self.copy_button, self.qr_button, self.export_button, self.save_button]

        run_hook('transaction_dialog', self)

        hbox = QHBoxLayout()
        hbox.addLayout(Buttons(*self.sharing_buttons))
        hbox.addStretch(1)
        hbox.addLayout(Buttons(*self.buttons))
        vbox.addLayout(hbox)
        self.update()