Files
purple-electrumwallet/electrum/gui/qt/wallet_info_dialog.py
T
f321x fb96ba41cd qt: WalletInfoDialog: sort keystores by root fingerprint
Sort the keystore tabs of the WalletInfoDialog by their root
fingerprints. This makes it less confusing when looking at different
wallet instances of the same multisig setup as the tabs will always have
the same order.
2026-01-08 09:53:41 +01:00

217 lines
10 KiB
Python

# Copyright (C) 2023 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import os
from typing import TYPE_CHECKING
from functools import partial
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QLabel, QVBoxLayout, QGridLayout,
QHBoxLayout, QPushButton, QWidget, QTabWidget)
from electrum.plugin import run_hook
from electrum.i18n import _
from electrum.wallet import Multisig_Wallet
from electrum.wizard import WizardViewState
from .main_window import protected
from electrum.gui.qt.wizard.wallet import QEKeystoreWizard
from .qrtextedit import ShowQRTextEdit
from .util import (
read_QIcon, WindowModalDialog, Buttons,
WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit
)
if TYPE_CHECKING:
from .main_window import ElectrumWindow
class WalletInfoDialog(WindowModalDialog):
def __init__(self, parent: QWidget, *, window: 'ElectrumWindow'):
WindowModalDialog.__init__(self, parent, _("Wallet Information"))
self.setMinimumSize(800, 100)
self.window = window
self.wallet = wallet = window.wallet
# required for @protected decorator
self._protected_requires_password = lambda: self.wallet.has_keystore_encryption() or self.wallet.storage.is_encrypted_with_user_pw()
config = window.config
vbox = QVBoxLayout()
wallet_type = wallet.db.get('wallet_type', '')
if wallet.is_watching_only():
wallet_type += ' [{}]'.format(_('watching-only'))
seed_available = _('False')
if wallet.has_seed():
seed_available = _('True')
seed_available += f" ({wallet.get_seed_type()})"
keystore_types = [k.get_type_text() for k in wallet.get_keystores()]
grid = QGridLayout()
basename = os.path.basename(wallet.storage.path)
cur_row = 0
grid.addWidget(WWLabel(_("Wallet name")+ ':'), cur_row, 0)
grid.addWidget(WWLabel(basename), cur_row, 1)
cur_row += 1
if db_metadata := wallet.db.get_db_metadata():
grid.addWidget(WWLabel(_("File created") + ':'), cur_row, 0)
grid.addWidget(WWLabel(db_metadata.to_str()), cur_row, 1)
cur_row += 1
grid.addWidget(WWLabel(_("Wallet type")+ ':'), cur_row, 0)
grid.addWidget(WWLabel(wallet_type), cur_row, 1)
cur_row += 1
grid.addWidget(WWLabel(_("Script type")+ ':'), cur_row, 0)
grid.addWidget(WWLabel(wallet.txin_type), cur_row, 1)
cur_row += 1
grid.addWidget(WWLabel(_("Seed available") + ':'), cur_row, 0)
grid.addWidget(WWLabel(str(seed_available)), cur_row, 1)
cur_row += 1
if len(keystore_types) <= 1:
grid.addWidget(WWLabel(_("Keystore type") + ':'), cur_row, 0)
ks_type = str(keystore_types[0]) if keystore_types else _('No keystore')
grid.addWidget(WWLabel(ks_type), cur_row, 1)
cur_row += 1
# lightning
grid.addWidget(WWLabel(_('Lightning') + ':'), cur_row, 0)
from .util import IconLabel
if wallet.has_lightning():
if wallet.lnworker.has_deterministic_node_id():
grid.addWidget(WWLabel(_('Enabled')), cur_row, 1)
else:
label = IconLabel(text='Enabled, non-recoverable channels')
label.setIcon(read_QIcon('cloud_no'))
grid.addWidget(label, cur_row, 1)
if wallet.get_seed_type() == 'segwit':
msg = _("Your channels cannot be recovered from seed, because they were created with an old version of Electrum. "
"This means that you must save a backup of your wallet every time you create a new channel.\n\n"
"If you want this wallet to have recoverable channels, you must close your existing channels and restore this wallet from seed")
else:
msg = _("Your channels cannot be recovered from seed. "
"This means that you must save a backup of your wallet every time you create a new channel.\n\n"
"If you want to have recoverable channels, you must create a new wallet with an Electrum seed")
grid.addWidget(HelpButton(msg), cur_row, 3)
cur_row += 1
grid.addWidget(WWLabel(_('Lightning Node ID:')), cur_row, 0)
cur_row += 1
nodeid_text = wallet.lnworker.node_keypair.pubkey.hex()
nodeid_e = ShowQRLineEdit(nodeid_text, config, title=_("Node ID"))
grid.addWidget(nodeid_e, cur_row, 0, 1, 4)
cur_row += 1
else:
if wallet.can_have_lightning():
grid.addWidget(WWLabel('Not enabled'), cur_row, 1)
button = QPushButton(_("Enable"))
button.pressed.connect(lambda: window.init_lightning_dialog(self))
grid.addWidget(button, cur_row, 3)
else:
grid.addWidget(WWLabel(_("Not available for this wallet.")), cur_row, 1)
grid.addWidget(HelpButton(_("Lightning is currently restricted to HD wallets with p2wpkh addresses.")), cur_row, 2)
cur_row += 1
vbox.addLayout(grid)
labels_clayout = None
if wallet.is_deterministic():
keystores = sorted(wallet.get_keystores(), key=lambda _ks: _ks.get_root_fingerprint() or '')
self.keystore_tabs = QTabWidget()
for idx, ks in enumerate(keystores):
ks_w = QWidget()
ks_vbox = QVBoxLayout()
ks_w.setLayout(ks_vbox)
status_label = _('This keystore is watching-only (disabled)') if ks.is_watching_only() else _('This keystore is active (enabled)')
ks_vbox.addWidget(QLabel(status_label))
label = f'{ks.label}' if hasattr(ks, 'label') and ks.label else ''
ks_vbox.addWidget(QLabel(_('Type') + ': ' + f'{ks.get_type_text()}' + ' ' + label))
mpk_text = ShowQRTextEdit(ks.get_master_public_key(), config=config)
mpk_text.setMaximumHeight(max(150, 10 * font_height()))
mpk_text.addCopyButton()
run_hook('show_xpub_button', mpk_text, ks)
ks_vbox.addWidget(WWLabel(_("Master Public Key")))
ks_vbox.addWidget(mpk_text)
der_path_hbox = QHBoxLayout()
der_path_hbox.setContentsMargins(0, 0, 0, 0)
der_path_hbox.addWidget(WWLabel(_("Derivation path") + ':'))
der_path_text = WWLabel(ks.get_derivation_prefix() or _("unknown"))
der_path_text.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
der_path_hbox.addWidget(der_path_text)
der_path_hbox.addStretch()
ks_vbox.addLayout(der_path_hbox)
bip32fp_hbox = QHBoxLayout()
bip32fp_hbox.setContentsMargins(0, 0, 0, 0)
bip32fp_hbox.addWidget(QLabel("BIP32 root fingerprint:"))
bip32fp_text = WWLabel(ks.get_root_fingerprint() or _("unknown"))
bip32fp_text.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
bip32fp_hbox.addWidget(bip32fp_text)
bip32fp_hbox.addStretch()
ks_vbox.addLayout(bip32fp_hbox)
if wallet.can_enable_disable_keystore(ks):
ks_buttons = []
if not ks.is_watching_only():
rm_keystore_button = QPushButton('Disable keystore')
rm_keystore_button.clicked.connect(partial(self.disable_keystore, ks))
ks_buttons.insert(0, rm_keystore_button)
else:
add_keystore_button = QPushButton('Enable Keystore')
add_keystore_button.clicked.connect(self.enable_keystore)
ks_buttons.insert(0, add_keystore_button)
ks_vbox.addLayout(Buttons(*ks_buttons))
tab_label = _("Cosigner") + f' {idx+1}' if len(keystores) > 1 else _("Keystore")
index = self.keystore_tabs.addTab(ks_w, tab_label)
if not ks.is_watching_only():
self.keystore_tabs.setTabIcon(index, read_QIcon('confirmed.svg'))
vbox.addWidget(self.keystore_tabs)
vbox.addStretch(1)
buttons = [CloseButton(self)]
btn_export_info = run_hook('wallet_info_buttons', window, self)
if btn_export_info is None:
btn_export_info = []
buttons = btn_export_info + buttons
btns = Buttons(*buttons)
vbox.addLayout(btns)
self.setLayout(vbox)
def disable_keystore(self, keystore):
if self.wallet.has_channels():
self.window.show_message(_('Cannot disable keystore: You have active lightning channels'))
return
msg = _('Disable keystore? This will make the keystore watching-only.')
if self.wallet.storage.is_encrypted_with_hw_device():
msg += '\n\n' + _('Note that this will disable wallet file encryption, because it uses your hardware wallet device.')
if not self.window.question(msg):
return
self.accept()
self.wallet.disable_keystore(keystore)
self.window.gui_object.reload_windows()
def enable_keystore(self, b: bool):
v = WizardViewState('keystore_type', {'wallet_type': self.window.wallet.wallet_type}, {})
dialog = QEKeystoreWizard(config=self.window.config, app=self.window.gui_object.app,
plugins=self.window.gui_object.plugins, start_viewstate=v)
result = dialog.run()
if not result:
return
keystore, is_hardware = result
for k in self.wallet.get_keystores():
if k.get_master_public_key() == keystore.get_master_public_key():
break
else:
self.window.show_error(_('Keystore not found in this wallet'))
return
self._enable_keystore(keystore, is_hardware)
@protected
def _enable_keystore(self, keystore, is_hardware, password):
self.accept()
self.wallet.enable_keystore(keystore, is_hardware, password)
self.window.gui_object.reload_windows()