def title(self): return _("Toggle Encryption")
def get_toolbar_buttons(self): return QLabel(_("Filter:")), self.change_button, self.used_button
def seed_img(self, is_seed=True): if is_seed: try: cseed = self.get_seed() except UserCancelled: return except InvalidPassword as e: self.d.show_error(str(e)) return if not cseed: self.d.show_message(_("This wallet has no seed")) return txt = cseed.upper() else: txt = self.txt.upper() img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap.fill(Qt.white) painter = QPainter() painter.begin(bitmap) QFontDatabase.addApplicationFont( os.path.join(os.path.dirname(__file__), 'SourceSansPro-Bold.otf')) if len(txt) < 102: fontsize = 15 linespace = 15 max_letters = 17 max_lines = 6 max_words = 3 else: fontsize = 12 linespace = 10 max_letters = 21 max_lines = 9 max_words = int(max_letters / 4) font = QFont('Source Sans Pro', fontsize, QFont.Bold) font.setLetterSpacing(QFont.PercentageSpacing, 100) font.setPixelSize(fontsize) painter.setFont(font) seed_array = txt.split(' ') for n in range(max_lines): nwords = max_words temp_seed = seed_array[:nwords] while len(' '.join(map(str, temp_seed))) > max_letters: nwords = nwords - 1 temp_seed = seed_array[:nwords] painter.drawText( QRect(0, linespace * n, self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed))) del seed_array[:nwords] painter.end() img = bitmap.toImage() if (self.rawnoise == False): self.make_rawnoise() self.make_cypherseed(img, self.rawnoise, False, is_seed) return img
def on_success(result): window.show_message( _("Your transaction was sent to the cosigning pool.") + '\n' + _("Open your cosigner wallet to retrieve it."))
def create_menu(self, position): from electrum_mona.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) if not idx.isValid(): return 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: webopen(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) if coins: menu.addAction(_("Spend from"), lambda: self.parent.spend_coins(coins)) run_hook('receive_menu', menu, addrs, self.wallet) menu.exec_(self.viewport().mapToGlobal(position))
def transaction_dialog(self, d): d.cosigner_send_button = b = QPushButton(_("Send to cosigner")) b.clicked.connect(lambda: self.do_send(d.tx)) d.buttons.insert(0, b) self.transaction_dialog_update(d)
def default_message(self): return _('Enter your password to proceed' ) if self.password_required else _('Click Send to proceed')
def update_io(self): inputs_header_text = _("Inputs") + ' (%d)' % len(self.tx.inputs()) if not self.finalized: selected_coins = self.main_window.get_manually_selected_coins() if selected_coins is not None: inputs_header_text += f" - " + _( "Coin selection active ({} UTXOs selected)").format( len(selected_coins)) self.inputs_header.setText(inputs_header_text) ext = QTextCharFormat() rec = QTextCharFormat() rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True))) rec.setToolTip(_("Wallet receive address")) chg = QTextCharFormat() chg.setBackground(QBrush(ColorScheme.YELLOW.as_color(background=True))) chg.setToolTip(_("Wallet change address")) twofactor = QTextCharFormat() twofactor.setBackground( QBrush(ColorScheme.BLUE.as_color(background=True))) twofactor.setToolTip( _("TrustedCoin (2FA) fee for the next batch of transactions")) def text_format(addr): if self.wallet.is_mine(addr): return chg if self.wallet.is_change(addr) else rec elif self.wallet.is_billing_address(addr): return twofactor return ext def format_amount(amt): return self.main_window.format_amount(amt, whitespaces=True) i_text = self.inputs_textedit i_text.clear() i_text.setFont(QFont(MONOSPACE_FONT)) i_text.setReadOnly(True) cursor = i_text.textCursor() for txin in self.tx.inputs(): if txin.is_coinbase_input(): cursor.insertText('coinbase') else: prevout_hash = txin.prevout.txid.hex() prevout_n = txin.prevout.out_idx cursor.insertText(prevout_hash + ":%-4d " % prevout_n, ext) addr = self.wallet.get_txin_address(txin) if addr is None: addr = '' cursor.insertText(addr, text_format(addr)) if isinstance( txin, PartialTxInput) and txin.value_sats() is not None: cursor.insertText(format_amount(txin.value_sats()), ext) cursor.insertBlock() self.outputs_header.setText( _("Outputs") + ' (%d)' % len(self.tx.outputs())) o_text = self.outputs_textedit o_text.clear() o_text.setFont(QFont(MONOSPACE_FONT)) o_text.setReadOnly(True) cursor = o_text.textCursor() for o in self.tx.outputs(): addr, v = o.get_ui_address_str(), o.value cursor.insertText(addr, text_format(addr)) if v is not None: cursor.insertText('\t', ext) cursor.insertText(format_amount(v), ext) cursor.insertBlock()
def add_tx_stats(self, vbox): hbox_stats = QHBoxLayout() # left column vbox_left = QVBoxLayout() self.tx_desc = TxDetailLabel(word_wrap=True) vbox_left.addWidget(self.tx_desc) self.status_label = TxDetailLabel() vbox_left.addWidget(self.status_label) self.date_label = TxDetailLabel() vbox_left.addWidget(self.date_label) self.amount_label = TxDetailLabel() vbox_left.addWidget(self.amount_label) self.ln_amount_label = TxDetailLabel() vbox_left.addWidget(self.ln_amount_label) fee_hbox = QHBoxLayout() self.fee_label = TxDetailLabel() fee_hbox.addWidget(self.fee_label) self.fee_warning_icon = QLabel() pixmap = QPixmap(icon_path("warning")) pixmap_size = round(2 * char_width_in_lineedit()) pixmap = pixmap.scaled(pixmap_size, pixmap_size, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.fee_warning_icon.setPixmap(pixmap) self.fee_warning_icon.setVisible(False) fee_hbox.addWidget(self.fee_warning_icon) fee_hbox.addStretch(1) vbox_left.addLayout(fee_hbox) vbox_left.addStretch(1) hbox_stats.addLayout(vbox_left, 50) # vertical line separator line_separator = QFrame() line_separator.setFrameShape(QFrame.VLine) line_separator.setFrameShadow(QFrame.Sunken) line_separator.setLineWidth(1) hbox_stats.addWidget(line_separator) # right column vbox_right = QVBoxLayout() self.size_label = TxDetailLabel() vbox_right.addWidget(self.size_label) self.rbf_label = TxDetailLabel() vbox_right.addWidget(self.rbf_label) self.rbf_cb = QCheckBox(_('Replace by fee')) self.rbf_cb.setChecked(bool(self.config.get('use_rbf', True))) vbox_right.addWidget(self.rbf_cb) self.locktime_final_label = TxDetailLabel() vbox_right.addWidget(self.locktime_final_label) locktime_setter_hbox = QHBoxLayout() locktime_setter_hbox.setContentsMargins(0, 0, 0, 0) locktime_setter_hbox.setSpacing(0) locktime_setter_label = TxDetailLabel() locktime_setter_label.setText("LockTime: ") self.locktime_e = LockTimeEdit() locktime_setter_hbox.addWidget(locktime_setter_label) locktime_setter_hbox.addWidget(self.locktime_e) locktime_setter_hbox.addStretch(1) self.locktime_setter_widget = QWidget() self.locktime_setter_widget.setLayout(locktime_setter_hbox) vbox_right.addWidget(self.locktime_setter_widget) self.block_height_label = TxDetailLabel() vbox_right.addWidget(self.block_height_label) vbox_right.addStretch(1) hbox_stats.addLayout(vbox_right, 50) vbox.addLayout(hbox_stats) # below columns self.block_hash_label = TxDetailLabel(word_wrap=True) vbox.addWidget(self.block_hash_label) # set visibility after parenting can be determined by Qt self.rbf_label.setVisible(self.finalized) self.rbf_cb.setVisible(not self.finalized) self.locktime_final_label.setVisible(self.finalized) self.locktime_setter_widget.setVisible(not self.finalized)
def copy_to_clipboard(self, *, tx: Transaction = None): if tx is None: tx = self.tx self.main_window.do_copy(str(tx), title=_("Transaction"))
def update(self): if not self.finalized: self.update_fee_fields() self.finalize_button.setEnabled(self.can_finalize()) 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.wallet.get_warning_for_risk_of_burning_coins_as_fees( self.tx)) self.fee_warning_icon.setToolTip(str(risk_of_burning_coins)) self.fee_warning_icon.setVisible(bool(risk_of_burning_coins)) self.fee_label.setText(fee_str) self.size_label.setText(size_str) if ln_amount is None or ln_amount == 0: 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) if tx_details.is_lightning_funding_tx: self._ptx_join_txs_action.setEnabled(False) # would change txid 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)
def __init__(self, *, parent: 'ElectrumWindow', desc, prompt_if_unsaved, finalized: bool, external_keypairs=None): '''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) self.tx = None # type: Optional[Transaction] self.external_keypairs = external_keypairs self.finalized = finalized self.main_window = parent self.config = parent.config self.wallet = parent.wallet self.prompt_if_unsaved = prompt_if_unsaved self.saved = False self.desc = desc self.setMinimumWidth(950) self.set_title() self.psbt_only_widgets = [] # type: List[QWidget] 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.add_tx_stats(vbox) vbox.addSpacing(10) self.inputs_header = QLabel() vbox.addWidget(self.inputs_header) self.inputs_textedit = QTextEditWithDefaultSize() vbox.addWidget(self.inputs_textedit) self.outputs_header = QLabel() vbox.addWidget(self.outputs_header) self.outputs_textedit = QTextEditWithDefaultSize() vbox.addWidget(self.outputs_textedit) 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")) b.clicked.connect(self.save) self.cancel_button = b = QPushButton(_("Close")) b.clicked.connect(self.close) b.setDefault(True) self.export_actions_menu = export_actions_menu = QMenu() self.add_export_actions_to_menu(export_actions_menu) export_actions_menu.addSeparator() export_submenu = export_actions_menu.addMenu( _("For CoinJoin; strip privates")) self.add_export_actions_to_menu(export_submenu, gettx=self._gettx_for_coinjoin) self.psbt_only_widgets.append(export_submenu) export_submenu = export_actions_menu.addMenu( _("For hardware device; include xpubs")) self.add_export_actions_to_menu(export_submenu, gettx=self._gettx_for_hardware_device) self.psbt_only_widgets.append(export_submenu) self.export_actions_button = QToolButton() self.export_actions_button.setText(_("Export")) self.export_actions_button.setMenu(export_actions_menu) self.export_actions_button.setPopupMode(QToolButton.InstantPopup) self.finalize_button = QPushButton(_('Finalize')) self.finalize_button.clicked.connect(self.on_finalize) partial_tx_actions_menu = QMenu() ptx_merge_sigs_action = QAction(_("Merge signatures from"), self) ptx_merge_sigs_action.triggered.connect(self.merge_sigs) partial_tx_actions_menu.addAction(ptx_merge_sigs_action) self._ptx_join_txs_action = QAction(_("Join inputs/outputs"), self) self._ptx_join_txs_action.triggered.connect(self.join_tx_with_another) partial_tx_actions_menu.addAction(self._ptx_join_txs_action) self.partial_tx_actions_button = QToolButton() self.partial_tx_actions_button.setText(_("Combine")) self.partial_tx_actions_button.setMenu(partial_tx_actions_menu) self.partial_tx_actions_button.setPopupMode(QToolButton.InstantPopup) self.psbt_only_widgets.append(self.partial_tx_actions_button) # Action buttons self.buttons = [ self.partial_tx_actions_button, self.sign_button, self.broadcast_button, self.cancel_button ] # Transaction sharing buttons self.sharing_buttons = [ self.finalize_button, self.export_actions_button, self.save_button ] run_hook('transaction_dialog', self) if not self.finalized: self.create_fee_controls() vbox.addWidget(self.feecontrol_fields) self.hbox = hbox = QHBoxLayout() hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) vbox.addLayout(hbox) self.set_buttons_visibility() dialogs.append(self)
def __init__(self, msg, kind, OK_button, wallet=None, force_disable_encrypt_cb=False): self.wallet = wallet self.pw = PasswordLineEdit() self.new_pw = PasswordLineEdit() self.conf_pw = PasswordLineEdit() self.kind = kind self.OK_button = OK_button vbox = QVBoxLayout() label = QLabel(msg + "\n") label.setWordWrap(True) grid = QGridLayout() grid.setSpacing(8) grid.setColumnMinimumWidth(0, 150) grid.setColumnMinimumWidth(1, 100) grid.setColumnStretch(1,1) if kind == PW_PASSPHRASE: vbox.addWidget(label) msgs = [_('Passphrase:'), _('Confirm Passphrase:')] else: logo_grid = QGridLayout() logo_grid.setSpacing(8) logo_grid.setColumnMinimumWidth(0, 70) logo_grid.setColumnStretch(1,1) logo = QLabel() logo.setAlignment(Qt.AlignCenter) logo_grid.addWidget(logo, 0, 0) logo_grid.addWidget(label, 0, 1, 1, 2) vbox.addLayout(logo_grid) m1 = _('New Password:'******'Password:'******'Confirm Password:'******'Current Password:'******'Encrypt wallet file')) self.encrypt_cb.setEnabled(False) grid.addWidget(self.encrypt_cb, 4, 0, 1, 2) if kind == PW_PASSPHRASE: self.encrypt_cb.setVisible(False) def enable_OK(): ok = self.new_pw.text() == self.conf_pw.text() OK_button.setEnabled(ok) self.encrypt_cb.setEnabled(ok and bool(self.new_pw.text()) and not force_disable_encrypt_cb) self.new_pw.textChanged.connect(enable_OK) self.conf_pw.textChanged.connect(enable_OK) self.vbox = vbox
class PasswordLayout(object): titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")] def __init__(self, msg, kind, OK_button, wallet=None, force_disable_encrypt_cb=False): self.wallet = wallet self.pw = PasswordLineEdit() self.new_pw = PasswordLineEdit() self.conf_pw = PasswordLineEdit() self.kind = kind self.OK_button = OK_button vbox = QVBoxLayout() label = QLabel(msg + "\n") label.setWordWrap(True) grid = QGridLayout() grid.setSpacing(8) grid.setColumnMinimumWidth(0, 150) grid.setColumnMinimumWidth(1, 100) grid.setColumnStretch(1,1) if kind == PW_PASSPHRASE: vbox.addWidget(label) msgs = [_('Passphrase:'), _('Confirm Passphrase:')] else: logo_grid = QGridLayout() logo_grid.setSpacing(8) logo_grid.setColumnMinimumWidth(0, 70) logo_grid.setColumnStretch(1,1) logo = QLabel() logo.setAlignment(Qt.AlignCenter) logo_grid.addWidget(logo, 0, 0) logo_grid.addWidget(label, 0, 1, 1, 2) vbox.addLayout(logo_grid) m1 = _('New Password:'******'Password:'******'Confirm Password:'******'Current Password:'******'Encrypt wallet file')) self.encrypt_cb.setEnabled(False) grid.addWidget(self.encrypt_cb, 4, 0, 1, 2) if kind == PW_PASSPHRASE: self.encrypt_cb.setVisible(False) def enable_OK(): ok = self.new_pw.text() == self.conf_pw.text() OK_button.setEnabled(ok) self.encrypt_cb.setEnabled(ok and bool(self.new_pw.text()) and not force_disable_encrypt_cb) self.new_pw.textChanged.connect(enable_OK) self.conf_pw.textChanged.connect(enable_OK) self.vbox = vbox def title(self): return self.titles[self.kind] def layout(self): return self.vbox def pw_changed(self): password = self.new_pw.text() if password: colors = {"Weak":"Red", "Medium":"Blue", "Strong":"Green", "Very Strong":"Green"} strength = check_password_strength(password) label = (_("Password Strength") + ": " + "<font color=" + colors[strength] + ">" + strength + "</font>") else: label = "" self.pw_strength.setText(label) def old_password(self): if self.kind == PW_CHANGE: return self.pw.text() or None return None def new_password(self): pw = self.new_pw.text() # Empty passphrases are fine and returned empty. if pw == "" and self.kind != PW_PASSPHRASE: pw = None return pw def clear_password_fields(self): for field in [self.pw, self.new_pw, self.conf_pw]: field.clear()
def decrypt_message(self, pubkey, message, password): raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
def set_title(self): self.setWindowTitle( _("Create transaction") if not self.finalized else _("Transaction") )
def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() 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) if txin.get('prev_tx') is None: # and not Transaction.is_segwit_input(txin): # note: offline signing does not work atm even with segwit inputs for ledger raise Exception(_('Offline signing with {} is not supported.').format(self.device)) 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 \ and info[0][0] == 1: # "is on 'change' branch" 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]) if changePath: # we don't set meaningful outputAddress, amount and fees # as we only care about the alternateEncoding==True branch outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) else: 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]) if changePath: # we don't set meaningful outputAddress, amount and fees # as we only care about the alternateEncoding==True branch outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) else: 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 BTChipException as e: if e.sw == 0x6985: # cancelled by user return else: traceback.print_exc(file=sys.stderr) self.give_error(e, True) 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()
def create_fee_controls(self): self.size_e = TxSizeLabel() self.size_e.setAlignment(Qt.AlignCenter) self.size_e.setAmount(0) self.size_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) self.feerate_e = FeerateEdit(lambda: 0) self.feerate_e.setAmount(self.config.fee_per_byte()) self.feerate_e.textEdited.connect( partial(self.on_fee_or_feerate, self.feerate_e, False)) self.feerate_e.editingFinished.connect( partial(self.on_fee_or_feerate, self.feerate_e, True)) self.fee_e = BTCAmountEdit(self.main_window.get_decimal_point) self.fee_e.textEdited.connect( partial(self.on_fee_or_feerate, self.fee_e, False)) self.fee_e.editingFinished.connect( partial(self.on_fee_or_feerate, self.fee_e, True)) self.fee_e.textChanged.connect(self.entry_changed) self.feerate_e.textChanged.connect(self.entry_changed) self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback) self.fee_combo = FeeComboBox(self.fee_slider) self.fee_slider.setFixedWidth(self.fee_e.width()) def feerounding_onclick(): text = ( self.feerounding_text + '\n\n' + _('To somewhat protect your privacy, Electrum tries to create change with similar precision to other outputs.' ) + ' ' + _('At most 100 satoshis might be lost due to this rounding.') + ' ' + _("You can disable this setting in '{}'.").format( _('Preferences')) + '\n' + _('Also, dust is not kept as change, but added to the fee.') + '\n' + _('Also, when batching RBF transactions, BIP 125 imposes a lower bound on the fee.' )) self.show_message(title=_('Fee rounding'), msg=text) self.feerounding_icon = QToolButton() self.feerounding_icon.setIcon(read_QIcon('info.png')) self.feerounding_icon.setAutoRaise(True) self.feerounding_icon.clicked.connect(feerounding_onclick) self.feerounding_icon.setVisible(False) self.feecontrol_fields = QWidget() hbox = QHBoxLayout(self.feecontrol_fields) hbox.setContentsMargins(0, 0, 0, 0) grid = QGridLayout() grid.addWidget(QLabel(_("Target fee:")), 0, 0) grid.addWidget(self.feerate_e, 0, 1) grid.addWidget(self.size_e, 0, 2) grid.addWidget(self.fee_e, 0, 3) grid.addWidget(self.feerounding_icon, 0, 4) grid.addWidget(self.fee_slider, 1, 1) grid.addWidget(self.fee_combo, 1, 2) hbox.addLayout(grid) hbox.addStretch(1)
def __init__(self, *, window: 'ElectrumWindow', make_tx, output_value: Union[int, str], is_sweep: bool): TxEditor.__init__(self, window=window, make_tx=make_tx, output_value=output_value, is_sweep=is_sweep) WindowModalDialog.__init__(self, window, _("Confirm Transaction")) vbox = QVBoxLayout() self.setLayout(vbox) grid = QGridLayout() vbox.addLayout(grid) self.amount_label = QLabel('') grid.addWidget(QLabel(_("Amount to be sent") + ": "), 0, 0) grid.addWidget(self.amount_label, 0, 1) msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\ + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\ + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.') self.fee_label = QLabel('') grid.addWidget(HelpLabel(_("Mining fee") + ": ", msg), 1, 0) grid.addWidget(self.fee_label, 1, 1) self.extra_fee_label = QLabel(_("Additional fees") + ": ") self.extra_fee_label.setVisible(False) self.extra_fee_value = QLabel('') self.extra_fee_value.setVisible(False) grid.addWidget(self.extra_fee_label, 2, 0) grid.addWidget(self.extra_fee_value, 2, 1) self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback) self.fee_combo = FeeComboBox(self.fee_slider) grid.addWidget( HelpLabel(_("Fee rate") + ": ", self.fee_combo.help_msg), 5, 0) grid.addWidget(self.fee_slider, 5, 1) grid.addWidget(self.fee_combo, 5, 2) self.message_label = QLabel(self.default_message()) grid.addWidget(self.message_label, 6, 0, 1, -1) self.pw_label = QLabel(_('Password')) self.pw_label.setVisible(self.password_required) self.pw = PasswordLineEdit() self.pw.setVisible(self.password_required) grid.addWidget(self.pw_label, 8, 0) grid.addWidget(self.pw, 8, 1, 1, -1) self.preview_button = QPushButton(_('Advanced')) self.preview_button.clicked.connect(self.on_preview) grid.addWidget(self.preview_button, 0, 2) self.send_button = QPushButton(_('Send')) self.send_button.clicked.connect(self.on_send) self.send_button.setDefault(True) vbox.addLayout(Buttons(CancelButton(self), self.send_button)) BlockingWaitingDialog(window, _("Preparing transaction..."), self.update_tx) self.update() self.is_send = False
def set_feerounding_text(self, num_satoshis_added): self.feerounding_text = ( _('Additional {} satoshis are going to be added.').format( num_satoshis_added))
def transaction_dialog(self, d: 'TxDialog'): d.cosigner_send_button = b = QPushButton(_("Send to cosigner")) b.clicked.connect(lambda: self.do_send(d.tx)) d.buttons.insert(0, b) b.setVisible(False)
def get_toolbar(self): h = QHBoxLayout() h.addStretch() h.addWidget(EnterButton(_('Open Channel'), self.new_channel_dialog)) return h
def update(self): self.wallet = self.parent.wallet current_address = self.current_item_user_role(col=self.Columns.LABEL) if self.show_change == AddressTypeFilter.RECEIVING: addr_list = self.wallet.get_receiving_addresses() elif self.show_change == AddressTypeFilter.CHANGE: addr_list = self.wallet.get_change_addresses() else: addr_list = self.wallet.get_addresses() self.model().clear() self.refresh_headers() fx = self.parent.fx set_address = None for address in addr_list: num = self.wallet.get_address_history_len(address) label = self.wallet.labels.get(address, '') c, u, x = self.wallet.get_addr_balance(address) balance = c + u + x is_used_and_empty = self.wallet.is_used(address) and balance == 0 if self.show_used == AddressUsageStateFilter.UNUSED and (balance or is_used_and_empty): continue if self.show_used == AddressUsageStateFilter.FUNDED and balance == 0: continue if self.show_used == AddressUsageStateFilter.USED_AND_EMPTY and not is_used_and_empty: continue balance_text = self.parent.format_amount(balance, whitespaces=True) # create item if fx and fx.get_fiat_address_config(): rate = fx.exchange_rate() fiat_balance = fx.value_str(balance, rate) else: fiat_balance = '' labels = ['', address, label, balance_text, fiat_balance, "%d"%num] address_item = [QStandardItem(e) for e in labels] # align text and set fonts for i, item in enumerate(address_item): item.setTextAlignment(Qt.AlignVCenter) if i not in (self.Columns.TYPE, self.Columns.LABEL): item.setFont(QFont(MONOSPACE_FONT)) self.set_editability(address_item) address_item[self.Columns.FIAT_BALANCE].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) # setup column 0 if self.wallet.is_change(address): address_item[self.Columns.TYPE].setText(_('change')) address_item[self.Columns.TYPE].setBackground(ColorScheme.YELLOW.as_color(True)) else: address_item[self.Columns.TYPE].setText(_('receiving')) address_item[self.Columns.TYPE].setBackground(ColorScheme.GREEN.as_color(True)) address_item[self.Columns.LABEL].setData(address, Qt.UserRole) # setup column 1 if self.wallet.is_frozen_address(address): address_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True)) if self.wallet.is_beyond_limit(address): address_item[self.Columns.ADDRESS].setBackground(ColorScheme.RED.as_color(True)) # add item count = self.model().rowCount() self.model().insertRow(count, address_item) address_idx = self.model().index(count, self.Columns.LABEL) if address == current_address: set_address = QPersistentModelIndex(address_idx) self.set_current_idx(set_address) # show/hide columns if fx and fx.get_fiat_address_config(): self.showColumn(self.Columns.FIAT_BALANCE) else: self.hideColumn(self.Columns.FIAT_BALANCE) self.filter()
class ChannelsList(MyTreeView): update_rows = QtCore.pyqtSignal() update_single_row = QtCore.pyqtSignal(Channel) class Columns(IntEnum): SHORT_CHANID = 0 NODE_ID = 1 LOCAL_BALANCE = 2 REMOTE_BALANCE = 3 CHANNEL_STATUS = 4 headers = { Columns.SHORT_CHANID: _('Short Channel ID'), Columns.NODE_ID: _('Node ID'), Columns.LOCAL_BALANCE: _('Local'), Columns.REMOTE_BALANCE: _('Remote'), Columns.CHANNEL_STATUS: _('Status'), } def __init__(self, parent): super().__init__(parent, self.create_menu, stretch_column=self.Columns.NODE_ID, editable_columns=[]) self.setModel(QtGui.QStandardItemModel(self)) self.main_window = parent 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 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 return [ format_short_channel_id(chan.short_channel_id), bh2u(chan.node_id), labels[LOCAL], labels[REMOTE], chan.get_state() ] 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): 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): def task(): coro = self.lnworker.force_close_channel(channel_id) return self.network.run_from_another_thread(coro) if self.parent.question('Force-close channel?\nReclaimed funds will not be immediately available.'): 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 create_menu(self, position): menu = QMenu() idx = self.selectionModel().currentIndex() item = self.model().itemFromIndex(idx) if not item: return channel_id = idx.sibling(idx.row(), self.Columns.NODE_ID).data(ROLE_CHANNEL_ID) chan = self.lnworker.channels[channel_id] menu.addAction(_("Details..."), lambda: self.details(channel_id)) if not chan.is_closed(): menu.addAction(_("Close channel"), lambda: self.close_channel(channel_id)) menu.addAction(_("Force-close channel"), lambda: self.force_close(channel_id)) else: menu.addAction(_("Remove"), lambda: self.remove_channel(channel_id)) menu.exec_(self.viewport().mapToGlobal(position)) def details(self, channel_id): assert self.parent.wallet ChannelDetailsDialog(self.parent, channel_id).show() @QtCore.pyqtSlot(Channel) def do_update_single_row(self, chan): for row in range(self.model().rowCount()): item = self.model().item(row, self.Columns.NODE_ID) if item.data(ROLE_CHANNEL_ID) == chan.channel_id: for column, v in enumerate(self.format_fields(chan)): self.model().item(row, column).setData(v, QtCore.Qt.DisplayRole) @QtCore.pyqtSlot() def do_update_rows(self): self.model().clear() self.update_headers(self.headers) for chan in self.parent.wallet.lnworker.channels.values(): items = [QtGui.QStandardItem(x) for x in self.format_fields(chan)] self.set_editability(items) items[self.Columns.NODE_ID].setData(chan.channel_id, ROLE_CHANNEL_ID) self.model().insertRow(0, items) def get_toolbar(self): h = QHBoxLayout() h.addStretch() h.addWidget(EnterButton(_('Open Channel'), self.new_channel_dialog)) return h 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')) d.setMinimumWidth(700) vbox = QVBoxLayout(d) h = QGridLayout() local_nodeid = QLineEdit() local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey)) local_nodeid.setReadOnly(True) local_nodeid.setCursorPosition(0) remote_nodeid = QLineEdit() local_amt_inp = BTCAmountEdit(self.parent.get_decimal_point) local_amt_inp.setAmount(200000) push_amt_inp = BTCAmountEdit(self.parent.get_decimal_point) push_amt_inp.setAmount(0) h.addWidget(QLabel(_('Your Node ID')), 0, 0) h.addWidget(local_nodeid, 0, 1) h.addWidget(QLabel(_('Remote Node ID or connection string or invoice')), 1, 0) h.addWidget(remote_nodeid, 1, 1) h.addWidget(QLabel('Local amount'), 2, 0) h.addWidget(local_amt_inp, 2, 1) h.addWidget(QLabel('Push amount'), 3, 0) h.addWidget(push_amt_inp, 3, 1) vbox.addLayout(h) ok_button = OkButton(d) ok_button.setDefault(True) vbox.addLayout(Buttons(CancelButton(d), ok_button)) suggestion = lnworker.suggest_peer() or b'' remote_nodeid.setText(bh2u(suggestion)) remote_nodeid.setCursorPosition(0) if not d.exec_(): return local_amt = local_amt_inp.get_amount() push_amt = push_amt_inp.get_amount() connect_contents = str(remote_nodeid.text()).strip() self.parent.open_channel(connect_contents, local_amt, push_amt)
def ui_text(self) -> str: return { self.ALL: _('All'), self.RECEIVING: _('Receiving'), self.CHANGE: _('Change'), }[self]
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)
class ContactList(MyTreeView): class Columns(IntEnum): NAME = 0 ADDRESS = 1 headers = { Columns.NAME: _('Name'), Columns.ADDRESS: _('Address'), } filter_columns = [Columns.NAME, Columns.ADDRESS] ROLE_CONTACT_KEY = Qt.UserRole + 1000 def __init__(self, parent): super().__init__(parent, self.create_menu, stretch_column=self.Columns.NAME, editable_columns=[self.Columns.NAME]) self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) self.update() def on_edited(self, idx, edit_key, *, text): _type, prior_name = self.parent.contacts.pop(edit_key) self.parent.set_contact(text, edit_key) self.update() 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(self.ROLE_CONTACT_KEY) 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.parent.import_contacts()) menu.addAction(_("Export file"), lambda: self.parent.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)) def update(self): if self.maybe_defer_update(): return current_key = self.get_role_data_for_current_item(col=self.Columns.NAME, role=self.ROLE_CONTACT_KEY) 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, self.ROLE_CONTACT_KEY) 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) self.filter() run_hook('update_contacts_tab', self) def get_edit_key_from_coordinate(self, row, col): if col != self.Columns.NAME: return None return self.get_role_data_from_coordinate(row, col, role=self.ROLE_CONTACT_KEY)
from electrum_mona.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
def create_status_bar(self, parent): b = StatusBarButton(read_QIcon('revealer.png'), "Revealer " + _("secret backup utility"), partial(self.setup_dialog, parent)) parent.addPermanentWidget(b)
def change_homescreen(self, homescreen): self.msg = _("Confirm on your {} device to change your home screen") self.apply_settings(homescreen=homescreen)