From 0277950247a8d807df3cbce221ceeedca462bdc9 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 13 Sep 2024 16:20:51 +0200 Subject: [PATCH 1/9] qt: factor out remaining ChoicesLayout uses --- electrum/gui/qt/main_window.py | 10 ++++---- electrum/gui/qt/receive_tab.py | 3 ++- electrum/gui/qt/seed_dialog.py | 18 +++++++------- electrum/gui/qt/send_tab.py | 18 +++++++------- electrum/gui/qt/util.py | 34 --------------------------- electrum/gui/qt/wallet_info_dialog.py | 13 +++++----- electrum/gui/qt/wizard/wallet.py | 8 +++---- electrum/plugins/digitalbitbox/qt.py | 4 ++++ 8 files changed, 38 insertions(+), 70 deletions(-) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 88a152175b9d..0e5eb01be914 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -82,13 +82,13 @@ from .transaction_dialog import show_transaction from .fee_slider import FeeSlider, FeeComboBox from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog, - WindowModalDialog, ChoicesLayout, HelpLabel, Buttons, + WindowModalDialog, HelpLabel, Buttons, OkButton, InfoButton, WWLabel, TaskThread, CancelButton, CloseButton, HelpButton, MessageBoxMixin, EnterButton, import_meta_gui, export_meta_gui, filename_field, address_field, char_width_in_lineedit, webopen, TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT, - getOpenFileName, getSaveFileName, BlockingWaitingDialog, font_height) + getOpenFileName, getSaveFileName, BlockingWaitingDialog, font_height, ChoiceWidget) from .util import ButtonsLineEdit, ShowQRLineEdit from .util import QtEventListener, qt_event_listener, event_listener from .wizard.wallet import WIF_HELP_TEXT @@ -1376,15 +1376,15 @@ def query_choice(self, msg, choices, title=None, default_choice=None): title = _('Question') dialog = WindowModalDialog(self.top_level_window(), title=title) dialog.setMinimumWidth(400) - clayout = ChoicesLayout(msg, choices, checked_index=default_choice) + choice_widget = ChoiceWidget(message=msg, choices=choices, selected=default_choice) vbox = QVBoxLayout(dialog) - vbox.addLayout(clayout.layout()) + vbox.addWidget(choice_widget) cancel_button = CancelButton(dialog) vbox.addLayout(Buttons(cancel_button, OkButton(dialog))) cancel_button.setFocus() if not dialog.exec_(): return None - return clayout.selected_index() + return choice_widget.selected_key def handle_payment_identifier(self, text: str): pi = PaymentIdentifier(self.wallet, text) diff --git a/electrum/gui/qt/receive_tab.py b/electrum/gui/qt/receive_tab.py index 79c71ae5211b..cda97d188d9c 100644 --- a/electrum/gui/qt/receive_tab.py +++ b/electrum/gui/qt/receive_tab.py @@ -194,7 +194,8 @@ def expiry_dialog(self): _('For Lightning requests, payments will not be accepted after the expiration.'), ]) expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS - v = self.window.query_choice(msg, pr_expiration_values(), title=_('Expiry'), default_choice=expiry) + choices = list(pr_expiration_values().items()) + v = self.window.query_choice(msg, choices, title=_('Expiry'), default_choice=expiry) if v is None: return self.config.WALLET_PAYREQ_EXPIRY_SECONDS = v diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py index 68f645a7ec43..e97881b1fc22 100644 --- a/electrum/gui/qt/seed_dialog.py +++ b/electrum/gui/qt/seed_dialog.py @@ -37,8 +37,7 @@ from electrum import slip39 from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path, - EnterButton, CloseButton, WindowModalDialog, ColorScheme, - ChoicesLayout, font_height) + EnterButton, CloseButton, WindowModalDialog, ColorScheme, font_height, ChoiceWidget) from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .completion_text_edit import CompletionTextEdit @@ -87,15 +86,15 @@ def seed_options(self): ) if value in self.options or value == 'electrum' ] - seed_type_values = [t[0] for t in seed_types] if 'ext' in self.options: cb_ext = QCheckBox(_('Extend this seed with custom words')) cb_ext.setChecked(self.is_ext) vbox.addWidget(cb_ext) + if len(seed_types) >= 2: - def f(choices_layout): - self.seed_type = seed_type_values[choices_layout.selected_index()] + def on_selected(idx): + self.seed_type = seed_type_choice.selected_key self.is_seed = (lambda x: bool(x)) if self.seed_type != 'electrum' else self.saved_is_seed self.slip39_current_mnemonic_invalid = None self.seed_status.setText('') @@ -120,16 +119,15 @@ def f(choices_layout): self.initialize_completer() self.seed_warning.setText(msg) - checked_index = seed_type_values.index(self.seed_type) - titles = [t[1] for t in seed_types] - clayout = ChoicesLayout(_('Seed type'), titles, on_clicked=f, checked_index=checked_index) - vbox.addLayout(clayout.layout()) + seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=seed_types, selected=self.seed_type) + seed_type_choice.itemSelected.connect(on_selected) + vbox.addWidget(seed_type_choice) vbox.addLayout(Buttons(OkButton(dialog))) if not dialog.exec_(): return None self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False - self.seed_type = seed_type_values[clayout.selected_index()] if len(seed_types) >= 2 else 'electrum' + self.seed_type = seed_type_choice.selected_key if len(seed_types) >= 2 else 'electrum' self.updated.emit() def __init__( diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py index e14ae70e2f5a..8b59c3ed3643 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -672,31 +672,31 @@ def pay_lightning_invoice(self, invoice: Invoice): can_pay_with_swap = lnworker.suggest_swap_to_send(amount_sat, coins=coins) rebalance_suggestion = lnworker.suggest_rebalance_to_send(amount_sat) can_rebalance = bool(rebalance_suggestion) and self.window.num_tasks() == 0 - choices = {} + choices = [] if can_rebalance: msg = ''.join([ _('Rebalance existing channels'), '\n', _('Move funds between your channels in order to increase your sending capacity.') ]) - choices[0] = msg + choices.append(('rebalance', msg)) if can_pay_with_new_channel: msg = ''.join([ _('Open a new channel'), '\n', _('You will be able to pay once the channel is open.') ]) - choices[1] = msg + choices.append(('new_channel', msg)) if can_pay_with_swap: msg = ''.join([ _('Swap onchain funds for lightning funds'), '\n', _('You will be able to pay once the swap is confirmed.') ]) - choices[2] = msg + choices.append(('swap', msg)) if can_pay_onchain: msg = ''.join([ _('Pay onchain'), '\n', _('Funds will be sent to the invoice fallback address.') ]) - choices[3] = msg + choices.append(('onchain', msg)) msg = _('You cannot pay that invoice using Lightning.') if lnworker and lnworker.channels: num_sats_can_send = int(lnworker.num_sats_can_send()) @@ -709,16 +709,16 @@ def pay_lightning_invoice(self, invoice: Invoice): r = self.window.query_choice(msg, choices) if r is not None: self.save_pending_invoice() - if r == 0: + if r == 'rebalance': chan1, chan2, delta = rebalance_suggestion self.window.rebalance_dialog(chan1, chan2, amount_sat=delta) - elif r == 1: + elif r == 'new_channel': amount_sat, min_amount_sat = can_pay_with_new_channel self.window.new_channel_dialog(amount_sat=amount_sat, min_amount_sat=min_amount_sat) - elif r == 2: + elif r == 'swap': chan, swap_recv_amount_sat = can_pay_with_swap self.window.run_swap_dialog(is_reverse=False, recv_amount_sat=swap_recv_amount_sat, channels=[chan]) - elif r == 3: + elif r == 'onchain': self.pay_onchain_dialog(invoice.get_outputs(), nonlocal_only=True) return diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 28392fa233a2..a508d1a9e92e 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -422,40 +422,6 @@ def text_dialog( return txt.toPlainText() -class ChoicesLayout(object): - def __init__(self, msg, choices, on_clicked=None, checked_index=0): - vbox = QVBoxLayout() - if len(msg) > 50: - vbox.addWidget(WWLabel(msg)) - msg = "" - gb2 = QGroupBox(msg) - vbox.addWidget(gb2) - vbox2 = QVBoxLayout() - gb2.setLayout(vbox2) - self.group = group = QButtonGroup(gb2) - if isinstance(choices, list): - iterator = enumerate(choices) - else: - iterator = choices.items() - for i, c in iterator: - button = QRadioButton(gb2) - button.setText(c) - vbox2.addWidget(button) - group.addButton(button) - group.setId(button, i) - if i == checked_index: - button.setChecked(True) - if on_clicked: - group.buttonClicked.connect(partial(on_clicked, self)) - self.vbox = vbox - - def layout(self): - return self.vbox - - def selected_index(self): - return self.group.checkedId() - - class ChoiceWidget(QWidget): itemSelected = pyqtSignal([int], arguments=['index']) diff --git a/electrum/gui/qt/wallet_info_dialog.py b/electrum/gui/qt/wallet_info_dialog.py index 545e44f05ae1..2247cf3742ae 100644 --- a/electrum/gui/qt/wallet_info_dialog.py +++ b/electrum/gui/qt/wallet_info_dialog.py @@ -9,14 +9,13 @@ from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout, QPushButton, QWidget, QStackedWidget) -from electrum import keystore from electrum.plugin import run_hook from electrum.i18n import _ from electrum.wallet import Multisig_Wallet from .qrtextedit import ShowQRTextEdit -from .util import (read_QIcon, WindowModalDialog, ChoicesLayout, Buttons, - WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit) +from .util import (read_QIcon, WindowModalDialog, Buttons, + WWLabel, CloseButton, HelpButton, font_height, ShowQRLineEdit, ChoiceWidget) if TYPE_CHECKING: from .main_window import ElectrumWindow @@ -118,11 +117,11 @@ def label(idx, ks): else: return _("keystore") + f' {idx+1}' - labels = [label(idx, ks) for idx, ks in enumerate(wallet.get_keystores())] + labels = [(idx, label(idx, ks)) for idx, ks in enumerate(wallet.get_keystores())] - on_click = lambda clayout: select_ks(clayout.selected_index()) - labels_clayout = ChoicesLayout(_("Select keystore"), labels, on_click) - vbox.addLayout(labels_clayout.layout()) + keystore_choice = ChoiceWidget(message=_("Select keystore"), choices=labels) + keystore_choice.itemSelected.connect(lambda x: select_ks(x)) + vbox.addWidget(keystore_choice) for ks in keystores: ks_w = QWidget() diff --git a/electrum/gui/qt/wizard/wallet.py b/electrum/gui/qt/wizard/wallet.py index 749b49969681..b4da6beb37a9 100644 --- a/electrum/gui/qt/wizard/wallet.py +++ b/electrum/gui/qt/wizard/wallet.py @@ -29,7 +29,7 @@ from electrum.gui.qt.password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD, PasswordLayoutForHW from electrum.gui.qt.seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout from electrum.gui.qt.util import (PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height, - ChoiceWidget, MessageBoxMixin, WindowModalDialog, ChoicesLayout, CancelButton, + ChoiceWidget, MessageBoxMixin, WindowModalDialog, CancelButton, Buttons, OkButton, icon_path) if TYPE_CHECKING: @@ -243,15 +243,15 @@ def query_choice(self, msg, choices, title=None, default_choice=None): title = _('Question') dialog = WindowModalDialog(self.top_level_window(), title=title) dialog.setMinimumWidth(400) - clayout = ChoicesLayout(msg, choices, checked_index=default_choice) + choice_widget = ChoiceWidget(message=msg, choices=choices, selected=default_choice) vbox = QVBoxLayout(dialog) - vbox.addLayout(clayout.layout()) + vbox.addWidget(choice_widget) cancel_button = CancelButton(dialog) vbox.addLayout(Buttons(cancel_button, OkButton(dialog))) cancel_button.setFocus() if not dialog.exec_(): return None - return clayout.selected_index() + return choice_widget.selected_key class WalletWizardComponent(WizardComponent, ABC): diff --git a/electrum/plugins/digitalbitbox/qt.py b/electrum/plugins/digitalbitbox/qt.py index d72389b23fd4..38826297cb3e 100644 --- a/electrum/plugins/digitalbitbox/qt.py +++ b/electrum/plugins/digitalbitbox/qt.py @@ -69,6 +69,10 @@ class DigitalBitbox_Handler(QtHandlerBase): def __init__(self, win): super(DigitalBitbox_Handler, self).__init__(win, 'Digital Bitbox') + def query_choice(self, msg, labels): + choices = [(i, v) for i, v in enumerate(labels)] + return QtHandlerBase.query_choice(self, msg, choices) + class WCDigitalBitboxScriptAndDerivation(WCScriptAndDerivation): requestRecheck = pyqtSignal() From 1d9ff40d0b5bfa7c53db477d133e5967cf59a8b2 Mon Sep 17 00:00:00 2001 From: wakiyamap Date: Sat, 14 Sep 2024 01:47:49 +0900 Subject: [PATCH 2/9] Add suport testnet4 --- electrum/checkpoints_testnet4.json | 90 ++++++++++++++++++++++++++++++ electrum/commands.py | 1 + electrum/constants.py | 15 +++++ electrum/servers_testnet4.json | 20 +++++++ electrum/simple_config.py | 3 + electrum/trampoline.py | 4 ++ electrum/util.py | 7 +++ run_electrum | 2 + 8 files changed, 142 insertions(+) create mode 100644 electrum/checkpoints_testnet4.json create mode 100644 electrum/servers_testnet4.json diff --git a/electrum/checkpoints_testnet4.json b/electrum/checkpoints_testnet4.json new file mode 100644 index 000000000000..8826376dd2cd --- /dev/null +++ b/electrum/checkpoints_testnet4.json @@ -0,0 +1,90 @@ +[ + [ + "00000000962a7fc2ef639196051fe181ed53ac6aa4cdfead14dca90f58aa36bc", + 0 + ], + [ + "000000002ad661157c553c0bbbb2490407adb1c8ac09f2b2a7174f87eeeb64bf", + 0 + ], + [ + "000000000be3ff43cde9eed4d6b2d4ad16c4f9509ccb94e1001af68e2f6647b3", + 0 + ], + [ + "00000000001ef2e4c2fc174354ed357cf313725fc336092733b2699d36342ff8", + 0 + ], + [ + "000000000025269f9fa4b0832ccbfef682d59c0fa8845b0c22cc24a1973f011a", + 0 + ], + [ + "000000000014b2d6b2ad804d5deb8d5b4a58caf152f6cea5600af0d9348dff29", + 0 + ], + [ + "000000000003c067c302d43c9499da6e382260252a2a29caf9748ee6972d5f01", + 0 + ], + [ + "000000000002180d23f15ba0b8161d9d38d03c61ab51d050c57928e1a7d98e0c", + 0 + ], + [ + "000000000000ed8722220a13b09d968a59686af5fc5c1e0a86371a498209fa72", + 0 + ], + [ + "0000000000003e82df3830ff7c05a58745a463a59d1097e160e47ac7aeb5323a", + 0 + ], + [ + "0000000000000c3f18b9a30269c4b53dd107bacf20482e4ec660e9970999a99f", + 0 + ], + [ + "00000000000002901853780dc8a63efd4d72359d8de7e14dc0398ccfc53d45cd", + 0 + ], + [ + "00000000000000a6ff1615113d25eeed8554813e4994f8ef7ce96458083d14cf", + 0 + ], + [ + "000000000000006e2d4fa8204c67c0986f9bb0214990b11043d0653d50755f54", + 0 + ], + [ + "00000000eaf8e0ea253d833614892aed70c55e5dc4b4d6709dd6420b8284debb", + 0 + ], + [ + "0000000000000063d3ca489d113ded6196c99f3785b61a8ded9254ebb96bc765", + 0 + ], + [ + "000000000000003f684cab6cdb7fe6e98cb13318bb45acdc2d2e2d7405b8bcbe", + 0 + ], + [ + "000000000000001f735b5a23732fb201cf6343b373c94a35f04e6b6075591889", + 0 + ], + [ + "000000001c247a1eb479ecc56ea7d7529f0c4afb6b7025f437a7d235454cd6a4", + 0 + ], + [ + "00000000acd1400a4801f361d675644993ad05e5b735a881f26746ece767521e", + 0 + ], + [ + "00000000542792e54a720567ba66157d48cdae7bfd01c1b678d0f07a2ed56e99", + 0 + ], + [ + "00000000ca301f565989627133247615bc937b52c68f8f4b342b6c2aeebff7ba", + 0 + ] +] diff --git a/electrum/commands.py b/electrum/commands.py index a3dc424ef9b7..68c1c7fdb0e6 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -1590,6 +1590,7 @@ def add_global_options(parser): group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory") group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet") + group.add_argument("--testnet4", action="store_true", dest="testnet4", default=False, help="Use Testnet4") group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest") group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet") group.add_argument("--signet", action="store_true", dest="signet", default=False, help="Use Signet") diff --git a/electrum/constants.py b/electrum/constants.py index 445bd849055f..6ed16e6785d9 100644 --- a/electrum/constants.py +++ b/electrum/constants.py @@ -155,6 +155,17 @@ class BitcoinTestnet(AbstractNet): ] +class BitcoinTestnet4(BitcoinTestnet): + + NET_NAME = "testnet4" + SEGWIT_HRP = "tb" + BOLT11_HRP = SEGWIT_HRP + GENESIS = "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043" + DEFAULT_SERVERS = read_json('servers_testnet4.json', {}) + CHECKPOINTS = read_json('checkpoints_testnet4.json', []) + LN_DNS_SEEDS = [] + + class BitcoinRegtest(BitcoinTestnet): NET_NAME = "regtest" @@ -211,6 +222,10 @@ def set_testnet(): global net net = BitcoinTestnet +def set_testnet4(): + global net + net = BitcoinTestnet4 + def set_regtest(): global net net = BitcoinRegtest diff --git a/electrum/servers_testnet4.json b/electrum/servers_testnet4.json new file mode 100644 index 000000000000..37145d4420c9 --- /dev/null +++ b/electrum/servers_testnet4.json @@ -0,0 +1,20 @@ +{ + "127.0.0.1": { + "pruning": "-", + "s": "51002", + "t": "51001", + "version": "1.4" + }, + "testnet4-electrumx.wakiyamap.dev": { + "pruning": "-", + "s": "51002", + "t": "51001", + "version": "1.4" + }, + "blackie.c3-soft.com": { + "pruning": "-", + "s": "57010", + "t": "57009", + "version": "1.4" + } +} diff --git a/electrum/simple_config.py b/electrum/simple_config.py index c7cd8918eb5e..32190b1f4d1f 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -254,6 +254,9 @@ def electrum_path(self): if self.get('testnet'): path = os.path.join(path, 'testnet') make_dir(path, allow_symlink=False) + if self.get('testnet4'): + path = os.path.join(path, 'testnet4') + make_dir(path, allow_symlink=False) elif self.get('regtest'): path = os.path.join(path, 'regtest') make_dir(path, allow_symlink=False) diff --git a/electrum/trampoline.py b/electrum/trampoline.py index b8a6208efc42..8f0f1ed8bb9f 100644 --- a/electrum/trampoline.py +++ b/electrum/trampoline.py @@ -26,6 +26,8 @@ 'Electrum trampoline': LNPeerAddr(host='lightning.electrum.org', port=9739, pubkey=bytes.fromhex('02bf82e22f99dcd7ac1de4aad5152ce48f0694c46ec582567f379e0adbf81e2d0f')), } +TRAMPOLINE_NODES_TESTNET4 = {} + TRAMPOLINE_NODES_SIGNET = { 'lnd wakiyamap.dev': LNPeerAddr(host='signet-electrumx.wakiyamap.dev', port=9735, pubkey=bytes.fromhex('02dadf6c28f3284d591cd2a4189d1530c1ff82c07059ebea150a33ab76e7364b4a')), 'eclair wakiyamap.dev': LNPeerAddr(host='signet-eclair.wakiyamap.dev', port=9735, pubkey=bytes.fromhex('0271cf3881e6eadad960f47125434342e57e65b98a78afa99f9b4191c02dd7ab3b')), @@ -40,6 +42,8 @@ def hardcoded_trampoline_nodes() -> Mapping[str, LNPeerAddr]: return TRAMPOLINE_NODES_MAINNET elif constants.net.NET_NAME == "testnet": return TRAMPOLINE_NODES_TESTNET + elif constants.net.NET_NAME == "testnet4": + return TRAMPOLINE_NODES_TESTNET4 elif constants.net.NET_NAME == "signet": return TRAMPOLINE_NODES_SIGNET else: diff --git a/electrum/util.py b/electrum/util.py index 63369b755385..c480eaee4c92 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -987,6 +987,11 @@ def age( {'tx': 'tx/', 'addr': 'address/'}), } +testnet4_block_explorers = { + 'mempool.space': ('https://mempool.space/testnet4/', + {'tx': 'tx/', 'addr': 'address/'}), +} + signet_block_explorers = { 'bc-2.jp': ('https://explorer.bc-2.jp/', {'tx': 'tx/', 'addr': 'address/'}), @@ -1009,6 +1014,8 @@ def block_explorer_info(): from . import constants if constants.net.NET_NAME == "testnet": return testnet_block_explorers + elif constants.net.NET_NAME == "testnet4": + return testnet4_block_explorers elif constants.net.NET_NAME == "signet": return signet_block_explorers return mainnet_block_explorers diff --git a/run_electrum b/run_electrum index 0f6c2e974b6b..e82aa78f9f6c 100755 --- a/run_electrum +++ b/run_electrum @@ -383,6 +383,8 @@ def main(): if config.get('testnet'): constants.set_testnet() + elif config.get('testnet4'): + constants.set_testnet4() elif config.get('regtest'): constants.set_regtest() elif config.get('simnet'): From 1adc0ad4d9dc4aeff063992923b019fd19c3a4c8 Mon Sep 17 00:00:00 2001 From: wakiyamap Date: Sat, 14 Sep 2024 09:51:44 +0900 Subject: [PATCH 3/9] remove BOLT11_HRP_INV_DICT --- electrum/lnaddr.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index e3f94f1c2844..5c9a07cde86e 100644 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -108,9 +108,6 @@ def parse_fallback_addr(data5: Sequence[int], net: Type[AbstractNet]) -> Optiona return addr -BOLT11_HRP_INV_DICT = {net.BOLT11_HRP: net for net in constants.NETS_LIST} - - def tagged5(char: str, data5: Sequence[int]) -> Sequence[int]: assert len(data5) < (1 << 10) return [CHARSET_INVERSE[char], len(data5) >> 5, len(data5) & 31] + data5 @@ -420,18 +417,16 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr: addr = LnAddr() addr.pubkey = None + addr.net = net - m = re.search("[^\\d]+", hrp[2:]) - if m: - addr.net = BOLT11_HRP_INV_DICT[m.group(0)] - amountstr = hrp[2+m.end():] - # BOLT #11: - # - # A reader SHOULD indicate if amount is unspecified, otherwise it MUST - # multiply `amount` by the `multiplier` value (if any) to derive the - # amount required for payment. - if amountstr != '': - addr.amount = unshorten_amount(amountstr) + amountstr = hrp[2+len(net.BOLT11_HRP):] + # BOLT #11: + # + # A reader SHOULD indicate if amount is unspecified, otherwise it MUST + # multiply `amount` by the `multiplier` value (if any) to derive the + # amount required for payment. + if amountstr != '': + addr.amount = unshorten_amount(amountstr) addr.date = int_from_data5(data5_remaining[:7]) data5_remaining = data5_remaining[7:] From 0b09592ef1662501c9f940b770cd50954bcc55e3 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Sat, 14 Sep 2024 11:12:42 +0200 Subject: [PATCH 4/9] qt: move query_choice to MessageBoxMixin, document ChoiceWidget --- electrum/gui/qt/main_window.py | 22 +++---------------- electrum/gui/qt/util.py | 37 ++++++++++++++++++++++++++++---- electrum/gui/qt/wizard/wallet.py | 16 -------------- electrum/plugins/hw_wallet/qt.py | 6 +++--- 4 files changed, 39 insertions(+), 42 deletions(-) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 0e5eb01be914..9b8f7b4fb116 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -34,7 +34,7 @@ from functools import partial import queue import asyncio -from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set, Mapping +from typing import Optional, TYPE_CHECKING, Sequence, Union, Dict, Mapping import concurrent.futures from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics @@ -45,7 +45,7 @@ QHBoxLayout, QPushButton, QScrollArea, QTextEdit, QShortcut, QMainWindow, QInputDialog, QWidget, QSizePolicy, QStatusBar, QToolTip, - QMenu, QAction, QStackedWidget, QToolButton) + QMenu, QAction, QToolButton) import electrum from electrum.gui import messages @@ -88,7 +88,7 @@ import_meta_gui, export_meta_gui, filename_field, address_field, char_width_in_lineedit, webopen, TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT, - getOpenFileName, getSaveFileName, BlockingWaitingDialog, font_height, ChoiceWidget) + getOpenFileName, getSaveFileName, BlockingWaitingDialog, font_height) from .util import ButtonsLineEdit, ShowQRLineEdit from .util import QtEventListener, qt_event_listener, event_listener from .wizard.wallet import WIF_HELP_TEXT @@ -1370,22 +1370,6 @@ def on_open_channel_success(self, args): else: self.show_message(message) - def query_choice(self, msg, choices, title=None, default_choice=None): - # Needed by QtHandler for hardware wallets - if title is None: - title = _('Question') - dialog = WindowModalDialog(self.top_level_window(), title=title) - dialog.setMinimumWidth(400) - choice_widget = ChoiceWidget(message=msg, choices=choices, selected=default_choice) - vbox = QVBoxLayout(dialog) - vbox.addWidget(choice_widget) - cancel_button = CancelButton(dialog) - vbox.addLayout(Buttons(cancel_button, OkButton(dialog))) - cancel_button.setFocus() - if not dialog.exec_(): - return None - return choice_widget.selected_key - def handle_payment_identifier(self, text: str): pi = PaymentIdentifier(self.wallet, text) if pi.is_valid(): diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index a508d1a9e92e..27c38bf1104c 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -283,6 +283,26 @@ def msg_box(self, icon, parent, title, text, *, buttons=QMessageBox.Ok, rich_text=rich_text, checkbox=checkbox) + def query_choice(self, + msg: Optional[str], + choices: Sequence[Tuple], + title: Optional[str] = None, + default_choice: Optional[Any] = None) -> Optional[Any]: + # Needed by QtHandler for hardware wallets + if title is None: + title = _('Question') + dialog = WindowModalDialog(self.top_level_window(), title=title) + dialog.setMinimumWidth(400) + choice_widget = ChoiceWidget(message=msg, choices=choices, selected=default_choice) + vbox = QVBoxLayout(dialog) + vbox.addWidget(choice_widget) + cancel_button = CancelButton(dialog) + vbox.addLayout(Buttons(cancel_button, OkButton(dialog))) + cancel_button.setFocus() + if not dialog.exec_(): + return None + return choice_widget.selected_key + def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.Ok, defaultButton=QMessageBox.NoButton, rich_text=False, @@ -423,9 +443,18 @@ def text_dialog( class ChoiceWidget(QWidget): + """Renders a list of tuples as a radiobuttons group. + The first element of each tuple is used as a key. + The second element of each tuple is used as user facing string. + The remainder of the tuple can be any additional data. + Callers can pre-select an item by key, through the 'selected' parameter. + The selected item is made available by index (selected_index), + by key (selected_key) and by whole tuple (selected_item). + """ + itemSelected = pyqtSignal([int], arguments=['index']) - def __init__(self, *, message=None, choices=None, selected=None): + def __init__(self, *, message: Optional[str] = None, choices: Sequence[Tuple] = None, selected: Optional[Any] = None): QWidget.__init__(self) vbox = QVBoxLayout() self.setLayout(vbox) @@ -433,9 +462,9 @@ def __init__(self, *, message=None, choices=None, selected=None): if choices is None: choices = [] - self.selected_index = -1 - self.selected_item = None - self.selected_key = None + self.selected_index = -1 # int + self.selected_item = None # Optional[Tuple] + self.selected_key = None # Optional[Any] self.choices = choices diff --git a/electrum/gui/qt/wizard/wallet.py b/electrum/gui/qt/wizard/wallet.py index b4da6beb37a9..f39e9695949c 100644 --- a/electrum/gui/qt/wizard/wallet.py +++ b/electrum/gui/qt/wizard/wallet.py @@ -237,22 +237,6 @@ def task_wrap(_task): if on_finished: on_finished() - def query_choice(self, msg, choices, title=None, default_choice=None): - # Needed by QtHandler for hardware wallets - if title is None: - title = _('Question') - dialog = WindowModalDialog(self.top_level_window(), title=title) - dialog.setMinimumWidth(400) - choice_widget = ChoiceWidget(message=msg, choices=choices, selected=default_choice) - vbox = QVBoxLayout(dialog) - vbox.addWidget(choice_widget) - cancel_button = CancelButton(dialog) - vbox.addLayout(Buttons(cancel_button, OkButton(dialog))) - cancel_button.setFocus() - if not dialog.exec_(): - return None - return choice_widget.selected_key - class WalletWizardComponent(WizardComponent, ABC): # ^ this class only exists to help with typing diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index aedd16d6197b..9656019a03c4 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -26,7 +26,7 @@ import threading from functools import partial -from typing import TYPE_CHECKING, Union, Optional +from typing import TYPE_CHECKING, Union, Optional, Sequence, Tuple from PyQt5.QtCore import QObject, pyqtSignal, Qt from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel @@ -95,7 +95,7 @@ def _update_status(self, paired): icon_name = button.icon_paired if paired else button.icon_unpaired button.setIcon(read_QIcon(icon_name)) - def query_choice(self, msg, labels): + def query_choice(self, msg: str, labels: Sequence[Tuple]): self.done.clear() self.query_signal.emit(msg, labels) self.done.wait() @@ -194,7 +194,7 @@ def clear_dialog(self): self.dialog.accept() self.dialog = None - def win_query_choice(self, msg, labels): + def win_query_choice(self, msg: str, labels: Sequence[Tuple]): try: self.choice = self.win.query_choice(msg, labels) except UserCancelled: From 778aea340e21c9d23b3f9928f549846c6f0da2be Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 16 Sep 2024 15:11:03 +0000 Subject: [PATCH 5/9] follow-up testnet4 stuff: trivial clean-up re https://github.com/spesmilo/electrum/pull/9197 localhost in servers list should only be for regtest-like networks --- electrum/constants.py | 2 -- electrum/servers_testnet4.json | 6 ------ electrum/simple_config.py | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/electrum/constants.py b/electrum/constants.py index 6ed16e6785d9..28dde0432143 100644 --- a/electrum/constants.py +++ b/electrum/constants.py @@ -158,8 +158,6 @@ class BitcoinTestnet(AbstractNet): class BitcoinTestnet4(BitcoinTestnet): NET_NAME = "testnet4" - SEGWIT_HRP = "tb" - BOLT11_HRP = SEGWIT_HRP GENESIS = "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043" DEFAULT_SERVERS = read_json('servers_testnet4.json', {}) CHECKPOINTS = read_json('checkpoints_testnet4.json', []) diff --git a/electrum/servers_testnet4.json b/electrum/servers_testnet4.json index 37145d4420c9..3069a160722a 100644 --- a/electrum/servers_testnet4.json +++ b/electrum/servers_testnet4.json @@ -1,10 +1,4 @@ { - "127.0.0.1": { - "pruning": "-", - "s": "51002", - "t": "51001", - "version": "1.4" - }, "testnet4-electrumx.wakiyamap.dev": { "pruning": "-", "s": "51002", diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 32190b1f4d1f..23b3c7b1a9ae 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -254,7 +254,7 @@ def electrum_path(self): if self.get('testnet'): path = os.path.join(path, 'testnet') make_dir(path, allow_symlink=False) - if self.get('testnet4'): + elif self.get('testnet4'): path = os.path.join(path, 'testnet4') make_dir(path, allow_symlink=False) elif self.get('regtest'): From 1257f21b1bc65f2c53b1e000a3a53c9da2ffe870 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 16 Sep 2024 15:27:45 +0000 Subject: [PATCH 6/9] constants: rm set_testnet/set_mainnet fns with AbstractNet.set_as_network --- electrum/constants.py | 29 ++++------------------ electrum/scripts/ln_features.py | 2 +- electrum/scripts/quick_start.py | 2 +- electrum/scripts/servers.py | 2 +- electrum/scripts/update_default_servers.py | 2 +- run_electrum | 10 ++++---- tests/__init__.py | 8 +++--- tests/test_blockchain.py | 4 +-- tests/test_network.py | 8 +++--- tests/test_psbt.py | 4 +-- tests/test_simple_config.py | 4 +-- 11 files changed, 28 insertions(+), 47 deletions(-) diff --git a/electrum/constants.py b/electrum/constants.py index 28dde0432143..4dc8654ba167 100644 --- a/electrum/constants.py +++ b/electrum/constants.py @@ -76,6 +76,11 @@ def max_checkpoint(cls) -> int: def rev_genesis_bytes(cls) -> bytes: return bytes.fromhex(cls.GENESIS)[::-1] + @classmethod + def set_as_network(cls) -> None: + global net + net = cls + class BitcoinMainnet(AbstractNet): @@ -203,27 +208,3 @@ class BitcoinSignet(BitcoinTestnet): # don't import net directly, import the module instead (so that net is singleton) net = BitcoinMainnet # type: Type[AbstractNet] - -def set_signet(): - global net - net = BitcoinSignet - -def set_simnet(): - global net - net = BitcoinSimnet - -def set_mainnet(): - global net - net = BitcoinMainnet - -def set_testnet(): - global net - net = BitcoinTestnet - -def set_testnet4(): - global net - net = BitcoinTestnet4 - -def set_regtest(): - global net - net = BitcoinRegtest diff --git a/electrum/scripts/ln_features.py b/electrum/scripts/ln_features.py index 6cfe901d15ad..fd93bb51a0a4 100644 --- a/electrum/scripts/ln_features.py +++ b/electrum/scripts/ln_features.py @@ -39,7 +39,7 @@ time.sleep(2) if IS_TESTNET: - constants.set_testnet() + constants.BitcoinTestnet.set_as_network() daemon = Daemon(config, listen_jsonrpc=False) network = daemon.network assert network.asyncio_loop.is_running() diff --git a/electrum/scripts/quick_start.py b/electrum/scripts/quick_start.py index 2483334f6533..a7fba0d23284 100755 --- a/electrum/scripts/quick_start.py +++ b/electrum/scripts/quick_start.py @@ -16,7 +16,7 @@ loop, stopping_fut, loop_thread = create_and_start_event_loop() config = SimpleConfig({"testnet": True}) # to use ~/.electrum/testnet as datadir -constants.set_testnet() # to set testnet magic bytes +constants.BitcoinTestnet.set_as_network() # to set testnet magic bytes daemon = Daemon(config, listen_jsonrpc=False) network = daemon.network assert network.asyncio_loop.is_running() diff --git a/electrum/scripts/servers.py b/electrum/scripts/servers.py index c9ef3c613336..e3023d1e4193 100755 --- a/electrum/scripts/servers.py +++ b/electrum/scripts/servers.py @@ -8,7 +8,7 @@ from electrum import constants # testnet? -#constants.set_testnet() +#constants.BitcoinTestnet.set_as_network() config = SimpleConfig({'testnet': False}) loop, stopping_fut, loop_thread = create_and_start_event_loop() diff --git a/electrum/scripts/update_default_servers.py b/electrum/scripts/update_default_servers.py index b811b253d9f7..76b6ee17f904 100755 --- a/electrum/scripts/update_default_servers.py +++ b/electrum/scripts/update_default_servers.py @@ -37,7 +37,7 @@ def get_newly_added_servers(fname1, fname2=None): # testnet? -#constants.set_testnet() +#constants.BitcoinTestnet.set_as_network() config = SimpleConfig({'testnet': False}) loop, stopping_fut, loop_thread = create_and_start_event_loop() diff --git a/run_electrum b/run_electrum index e82aa78f9f6c..78d2a6187742 100755 --- a/run_electrum +++ b/run_electrum @@ -382,15 +382,15 @@ def main(): set_language(lang) if config.get('testnet'): - constants.set_testnet() + constants.BitcoinTestnet.set_as_network() elif config.get('testnet4'): - constants.set_testnet4() + constants.BitcoinTestnet4.set_as_network() elif config.get('regtest'): - constants.set_regtest() + constants.BitcoinRegtest.set_as_network() elif config.get('simnet'): - constants.set_simnet() + constants.BitcoinSimnet.set_as_network() elif config.get('signet'): - constants.set_signet() + constants.BitcoinSignet.set_as_network() # check if we received a valid payment identifier uri = config_options.get('url') diff --git a/tests/__init__.py b/tests/__init__.py index 8737da94023e..4e239f381d35 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -41,13 +41,13 @@ def __init__(self, *args, **kwargs): def setUpClass(cls): super().setUpClass() if cls.TESTNET: - constants.set_testnet() + constants.BitcoinTestnet.set_as_network() @classmethod def tearDownClass(cls): super().tearDownClass() if cls.TESTNET: - constants.set_mainnet() + constants.BitcoinMainnet.set_as_network() def setUp(self): self._test_lock.acquire() @@ -79,14 +79,14 @@ def as_testnet(func): if asyncio.iscoroutinefunction(func): async def run_test(*args, **kwargs): try: - constants.set_testnet() + constants.BitcoinTestnet.set_as_network() return await func(*args, **kwargs) finally: constants.net = old_net else: def run_test(*args, **kwargs): try: - constants.set_testnet() + constants.BitcoinTestnet.set_as_network() return func(*args, **kwargs) finally: constants.net = old_net diff --git a/tests/test_blockchain.py b/tests/test_blockchain.py index b16850045510..3a52ac654095 100644 --- a/tests/test_blockchain.py +++ b/tests/test_blockchain.py @@ -48,12 +48,12 @@ class TestBlockchain(ElectrumTestCase): @classmethod def setUpClass(cls): super().setUpClass() - constants.set_regtest() + constants.BitcoinRegtest.set_as_network() @classmethod def tearDownClass(cls): super().tearDownClass() - constants.set_mainnet() + constants.BitcoinMainnet.set_as_network() def setUp(self): super().setUp() diff --git a/tests/test_network.py b/tests/test_network.py index 07294057ca26..9afdbb769def 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -49,12 +49,12 @@ class TestNetwork(ElectrumTestCase): @classmethod def setUpClass(cls): super().setUpClass() - constants.set_regtest() + constants.BitcoinRegtest.set_as_network() @classmethod def tearDownClass(cls): super().tearDownClass() - constants.set_mainnet() + constants.BitcoinMainnet.set_as_network() async def asyncSetUp(self): await super().asyncSetUp() @@ -127,6 +127,6 @@ async def test_chain_false_during_binary(self): self.assertEqual(self.interface.q.qsize(), 0) -if __name__=="__main__": - constants.set_regtest() +if __name__ == "__main__": + constants.BitcoinRegtest.set_as_network() unittest.main() diff --git a/tests/test_psbt.py b/tests/test_psbt.py index 11338d2f6b09..090bcedef659 100644 --- a/tests/test_psbt.py +++ b/tests/test_psbt.py @@ -82,14 +82,14 @@ def test_valid_psbt_007(self): def test_valid_psbt_008(self): # Case: PSBT with `PSBT_GLOBAL_XPUB`. - constants.set_mainnet() + constants.BitcoinMainnet.set_as_network() try: tx1 = tx_from_any(bytes.fromhex('70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c503100008000000080000000800001011f00e1f5050000000016001433b982f91b28f160c920b4ab95e58ce50dda3a4a220203309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c47304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201220603309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c1827569c5031000080000000800000008000000000010000000001011f00e1f50500000000160014388fb944307eb77ef45197d0b0b245e079f011de220202c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b11047304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01220602c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b1101827569c5031000080000000800000008000000000000000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000')) tx2 = tx_from_any('cHNidP8BAJ0BAAAAAnEOp2q0XFy2Q45gflnMA3YmmBgFrp4N/ZCJASq7C+U1AQAAAAD/////GQmU1qizyMgsy8+y+6QQaqBmObhyqNRHRlwNQliNbWcAAAAAAP////8CAOH1BQAAAAAZdqkUtrwsDuVlWoQ9ea/t0MzD991kNAmIrGBa9AUAAAAAFgAUEYjvjkzgRJ6qyPsUHL9aEXbmoIgAAAAATwEEiLIeA55TDKyAAAAAPbyKXJdp8DGxfnf+oVGGAyIaGP0Y8rmlTGyMGsdcvDUC8jBYSxVdHH8c1FEgplPEjWULQxtnxbLBPyfXFCA3wWkQJ1acUDEAAIAAAACAAAAAgAABAR8A4fUFAAAAABYAFDO5gvkbKPFgySC0q5XljOUN2jpKIgIDMJaA8zx9446mpHzU7NZvH1pJdHxv+4gI7QkDkkPjrVxHMEQCIC1wTO2DDFapCTRL10K2hS3M0QPpY7rpLTjnUlTSu0JFAiAthsQ3GV30bAztoITyopHD2i1kBw92v5uQsZXn7yj3cgEiBgMwloDzPH3jjqakfNTs1m8fWkl0fG/7iAjtCQOSQ+OtXBgnVpxQMQAAgAAAAIAAAACAAAAAAAEAAAAAAQEfAOH1BQAAAAAWABQ4j7lEMH63fvRRl9CwskXgefAR3iICAsd3Fh9z0LfHK57nveZQKT0T8JW8dlatH1Jdpf0uELEQRzBEAiBMsftfhpyULg4mEAV2ElQ5F5rojcqKncO6CPeVOYj6pgIgUh9JynkcJ9cOJzybFGFphZCTYeJb4nTqIA1+CIJ+UU0BIgYCx3cWH3PQt8crnue95lApPRPwlbx2Vq0fUl2l/S4QsRAYJ1acUDEAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgLSDKUC7iiWhtIYFb1DqAY3sGmOH7zb5MrtRF9sGgqQ7xgnVpxQMQAAgAAAAIAAAACAAAAAAAQAAAAA') for tx in (tx1, tx2): self.assertEqual(1, len(tx.xpubs)) finally: - constants.set_testnet() + constants.BitcoinTestnet.set_as_network() def test_valid_psbt__input_with_both_witness_utxo_and_nonwitness_utxo(self): # Case: PSBT where an input has both WITNESS_UTXO and UTXO. diff --git a/tests/test_simple_config.py b/tests/test_simple_config.py index e06beadb6927..03b1a84dc19a 100644 --- a/tests/test_simple_config.py +++ b/tests/test_simple_config.py @@ -158,11 +158,11 @@ def test_configvars_get_default_value_complex_fn(self): config.SWAPSERVER_URL = None self.assertEqual("https://swaps.electrum.org/api", config.SWAPSERVER_URL) - constants.set_testnet() + constants.BitcoinTestnet.set_as_network() try: self.assertEqual("https://swaps.electrum.org/testnet", config.SWAPSERVER_URL) finally: - constants.set_mainnet() + constants.BitcoinMainnet.set_as_network() def test_configvars_convert_getter(self): config = SimpleConfig(self.options) From b3491b99d71b04f03a813a9016e9052602865d11 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 16 Sep 2024 15:48:30 +0000 Subject: [PATCH 7/9] build: update pinned qdarkstyle (partial rerun freeze_packages) related https://github.com/spesmilo/electrum/pull/9189 (version 3.2 added support for qt6, so this version supports both qt5 and qt6) --- contrib/deterministic-build/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 686da50c8524..88c9371e895e 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -32,8 +32,8 @@ protobuf==3.20.3 \ --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 python-socks==2.4.4 \ --hash=sha256:e5a8e4f78203612c813946feacd87b98943965a04389fe221fa1e9ab263ad22e -QDarkStyle==3.1 \ - --hash=sha256:600584d625343e0ddd128de08393d3c35637786a49827f174d29aa7caa8279c1 +QDarkStyle==3.2.3 \ + --hash=sha256:0c0b7f74a6e92121008992b369bab60468157db1c02cd30d64a5e9a3b402f1ae qrcode==7.3.1 \ --hash=sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578 QtPy==2.4.1 \ From a01ae99a6f06f0deb6e80ee520e1e595119b425f Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 17 Sep 2024 11:48:54 +0200 Subject: [PATCH 8/9] qt: fix scanning multi (privkeys, addresses) from QR. --- electrum/gui/qt/util.py | 42 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 27c38bf1104c..2d524aa6a828 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -680,14 +680,21 @@ def cb(success: bool, error: str, data: Optional[str]): return if not data: data = '' - if allow_multi: - new_text = self.text() + data + '\n' # TODO: unused? - else: - new_text = data - try: + try: + if allow_multi: + text = self.text() + if data in text: + return + if text and not text.endswith('\n'): + text += '\n' + text += data + text += '\n' + setText(text) + else: + new_text = data setText(new_text) - except Exception as e: - show_error(_('Invalid payment identifier in QR') + ':\n' + repr(e)) + except Exception as e: + show_error(_('Invalid payment identifier in QR') + ':\n' + repr(e)) from .qrreader import scan_qrcode if parent is None: @@ -725,14 +732,21 @@ def input_qr_from_screenshot( show_error(_("No QR code was found on the screen.")) return data = scanned_qr[0].data - if allow_multi: - new_text = self.text() + data + '\n' # TODO: unused? - else: - new_text = data - try: + try: + if allow_multi: + text = self.text() + if data in text: + return + if text and not text.endswith('\n'): + text += '\n' + text += data + text += '\n' + setText(text) + else: + new_text = data setText(new_text) - except Exception as e: - show_error(_('Invalid payment identifier in QR') + ':\n' + repr(e)) + except Exception as e: + show_error(_('Invalid payment identifier in QR') + ':\n' + repr(e)) def input_file( self, From eaebcaf383883f4892dbf3d002ce0fc822d15ce9 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 17 Sep 2024 13:21:46 +0200 Subject: [PATCH 9/9] qml: styling HelpDialog --- electrum/gui/qml/components/controls/HelpDialog.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/electrum/gui/qml/components/controls/HelpDialog.qml b/electrum/gui/qml/components/controls/HelpDialog.qml index d5aa4be1076a..f11a0d138d5b 100644 --- a/electrum/gui/qml/components/controls/HelpDialog.qml +++ b/electrum/gui/qml/components/controls/HelpDialog.qml @@ -30,9 +30,11 @@ ElDialog { ColumnLayout { id: rootLayout - width: dialog.parent.width * 2/3 + width: dialog.parent.width * 3/4 + spacing: constants.paddingLarge RowLayout { + Layout.fillWidth: true Image { source: Qt.resolvedUrl('../../../icons/info.png') Layout.preferredWidth: constants.iconSizeSmall @@ -40,15 +42,16 @@ ElDialog { } Label { text: dialog.heading + font.pixelSize: constants.fontSizeMedium font.underline: true font.italic: true } } - TextArea { + Label { id: message Layout.fillWidth: true - readOnly: true text: dialog.text + font.pixelSize: constants.fontSizeSmall wrapMode: TextInput.WordWrap textFormat: TextEdit.RichText background: Rectangle {