From 86ed0297a771b49287f87cc22ee1c940f45ef2fa Mon Sep 17 00:00:00 2001 From: bot Date: Sat, 20 Jun 2026 09:49:33 -0400 Subject: [PATCH] add wallet_util --- bal/wallet_util/README.md | 50 +++++++ bal/wallet_util/bal_wallet_utils.py | 81 ++++++++++ bal/wallet_util/bal_wallet_utils_qt.py | 199 +++++++++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 bal/wallet_util/README.md create mode 100755 bal/wallet_util/bal_wallet_utils.py create mode 100755 bal/wallet_util/bal_wallet_utils_qt.py diff --git a/bal/wallet_util/README.md b/bal/wallet_util/README.md new file mode 100644 index 0000000..0d8c02f --- /dev/null +++ b/bal/wallet_util/README.md @@ -0,0 +1,50 @@ +## README + +### Overview +This tool provides two entry points: a CLI script (bal_wallet_utils.py) and a Qt GUI script (bal_wallet_utils_qt.py) that operate against an Electrum source tree. + +### Installation / Preparation +1. Copy both files into the Electrum project root (the folder that contains the Electrum source package): + - bal_wallet_utils.py + - bal_wallet_utils_qt.py + +2. Activate the Electrum Python environment (the virtualenv used to run Electrum). Example (PowerShell, adjust path to your venv): +``` +.\env\Scripts\Activate.ps1 +``` +or (cmd): +``` +env\Scripts\activate.bat +``` + +### Running +- CLI version: +``` +python bal_wallet_utils.py +``` +- Qt GUI version: +``` +python bal_wallet_utils_qt.py +``` + +### Building a Windows executable with PyInstaller +From the project root (with the Electrum environment active), you can build the Qt executable using PyInstaller. Example command (adjust the paths if your environment path differs): +``` +pyinstaller.exe --onefile --noconsole --add-data "electrum\currencies.json;electrum" --add-data "electrum\bip39_wallet_formats.json;electrum" --add-data "electrum\lnwire\peer_wire.csv;electrum\lnwire" --add-data "electrum\lnwire\onion_wire.csv;electrum\lnwire" --add-binary "env/Lib/site-packages\electrum_ecc\libsecp256k1-6.dll;electrum_ecc" bal_wallet_utils_qt.py +``` + +Notes: +- Run the command from the project root so relative paths resolve correctly. +- On Windows the --add-data and --add-binary arguments use ";" to separate source and destination. +- If electrum expects additional data files or native DLLs, include them with additional --add-data / --add-binary flags. +- For debugging include --onedir first to inspect the created folder before using --onefile. + +### Troubleshooting +- If PyInstaller is not found, run it via Python: +``` +python -m PyInstaller +``` +- If the frozen exe fails because DLLs or JSON files are missing, add those files explicitly with --add-data or --add-binary. +- Test the build on a clean Windows VM to ensure all runtime dependencies are included. + +License and attribution: include your preferred license or attribution details here. diff --git a/bal/wallet_util/bal_wallet_utils.py b/bal/wallet_util/bal_wallet_utils.py new file mode 100755 index 0000000..b81e0a0 --- /dev/null +++ b/bal/wallet_util/bal_wallet_utils.py @@ -0,0 +1,81 @@ +#!env/bin/python3 +import getpass +import json +import os +import sys + +from electrum.storage import WalletStorage +from electrum.util import MyEncoder + +default_fees = 100 + + +def fix_will_settings_tx_fees(json_wallet): + tx_fees = json_wallet.get("will_settings", {}).get("tx_fees", False) + have_to_update = False + if tx_fees: + json_wallet["will_settings"]["baltx_fees"] = tx_fees + del json_wallet["will_settings"]["tx_fees"] + have_to_update = True + for txid, willitem in json_wallet["will"].items(): + tx_fees = willitem.get("tx_fees", False) + if tx_fees: + json_wallet["will"][txid]["baltx_fees"] = tx_fees + del json_wallet["will"][txid]["tx_fees"] + have_to_update = True + return have_to_update + + +def uninstall_bal(json_wallet): + if "will_settings" in json_wallet: + del json_wallet["will_settings"] + if "will" in json_wallet: + del json_wallet["will"] + if "heirs" in json_wallet: + del json_wallet["heirs"] + return True + + +def save(json_wallet, storage): + human_readable = not storage.is_encrypted() + storage.write( + json.dumps( + json_wallet, + indent=4 if human_readable else None, + sort_keys=bool(human_readable), + cls=MyEncoder, + ) + ) + + +def read_wallet(path, password=False): + storage = WalletStorage(path) + if storage.is_encrypted(): + if not password: + password = getpass.getpass("Enter wallet password: ", stream=None) + storage.decrypt(password) + data = storage.read() + json_wallet = json.loads("[" + data + "]")[0] + return json_wallet, storage + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("usage: ./bal_wallet_utils ") + print("available commands: uninstall, fix") + exit(1) + if not os.path.exists(sys.argv[2]): + print("Error: wallet not found") + exit(1) + command = sys.argv[1] + path = sys.argv[2] + json_wallet, storage = read_wallet(path) + have_to_save = False + if command == "fix": + have_to_save = fix_will_settings_tx_fees(json_wallet) + if command == "uninstall": + have_to_save = uninstall_bal(json_wallet) + if have_to_save: + save(json_wallet, storage) + else: + print("nothing to do") diff --git a/bal/wallet_util/bal_wallet_utils_qt.py b/bal/wallet_util/bal_wallet_utils_qt.py new file mode 100755 index 0000000..4da6b57 --- /dev/null +++ b/bal/wallet_util/bal_wallet_utils_qt.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +import json +import os +import sys + +from bal_wallet_utils import fix_will_settings_tx_fees, save, uninstall_bal +from electrum.storage import WalletStorage +from PyQt6.QtWidgets import ( + QApplication, + QFileDialog, + QGroupBox, + QHBoxLayout, + QLabel, + QLineEdit, + QMainWindow, + QPushButton, + QTextEdit, + QVBoxLayout, + QWidget, +) + + +class WalletUtilityGUI(QMainWindow): + def __init__(self): + super().__init__() + self.init_ui() + + def init_ui(self): + self.setWindowTitle("BAL Wallet Utility") + self.setFixedSize(500, 400) + + # Central widget + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Main layout + layout = QVBoxLayout(central_widget) + + # Wallet input group + wallet_group = QGroupBox("Wallet Settings") + wallet_layout = QVBoxLayout(wallet_group) + + # Wallet path + wallet_path_layout = QHBoxLayout() + wallet_path_layout.addWidget(QLabel("Wallet Path:")) + self.wallet_path_edit = QLineEdit() + self.wallet_path_edit.setPlaceholderText("Select wallet path...") + wallet_path_layout.addWidget(self.wallet_path_edit) + + self.browse_btn = QPushButton("Browse...") + self.browse_btn.clicked.connect(self.browse_wallet) + wallet_path_layout.addWidget(self.browse_btn) + + wallet_layout.addLayout(wallet_path_layout) + + # Password + password_layout = QHBoxLayout() + password_layout.addWidget(QLabel("Password:")) + self.password_edit = QLineEdit() + self.password_edit.setEchoMode(QLineEdit.EchoMode.Password) + self.password_edit.setPlaceholderText("Enter password (if encrypted)") + password_layout.addWidget(self.password_edit) + + wallet_layout.addLayout(password_layout) + + layout.addWidget(wallet_group) + + # Output area + output_group = QGroupBox("Output") + output_layout = QVBoxLayout(output_group) + + self.output_text = QTextEdit() + self.output_text.setReadOnly(True) + output_layout.addWidget(self.output_text) + + layout.addWidget(output_group) + + # Action buttons + buttons_layout = QHBoxLayout() + + self.fix_btn = QPushButton("Fix") + self.fix_btn.clicked.connect(self.fix_wallet) + self.fix_btn.setEnabled(False) + buttons_layout.addWidget(self.fix_btn) + + self.uninstall_btn = QPushButton("Uninstall") + self.uninstall_btn.clicked.connect(self.uninstall_wallet) + self.uninstall_btn.setEnabled(False) + buttons_layout.addWidget(self.uninstall_btn) + + layout.addLayout(buttons_layout) + + # Connections to enable buttons when path is entered + self.wallet_path_edit.textChanged.connect(self.check_inputs) + + def browse_wallet(self): + file_path, _ = QFileDialog.getOpenFileName( + self, "Select Wallet", "*", "Electrum Wallet (*)" + ) + if file_path: + self.wallet_path_edit.setText(file_path) + + def check_inputs(self): + wallet_path = self.wallet_path_edit.text().strip() + has_path = bool(wallet_path) and os.path.exists(wallet_path) + + self.fix_btn.setEnabled(has_path) + self.uninstall_btn.setEnabled(has_path) + + def log_message(self, message): + self.output_text.append(message) + + def fix_wallet(self): + self.process_wallet("fix") + + def uninstall_wallet(self): + self.log_message( + "WARNING: This will remove all BAL settings. This operation cannot be undone." + ) + self.process_wallet("uninstall") + + def process_wallet(self, command): + wallet_path = self.wallet_path_edit.text().strip() + password = self.password_edit.text() + + if not wallet_path: + self.log_message("ERROR: Please enter wallet path") + return + + if not os.path.exists(wallet_path): + self.log_message("ERROR: Wallet not found") + return + + try: + self.log_message(f"Processing wallet: {wallet_path}") + + storage = WalletStorage(wallet_path) + + # Decrypt if necessary + if storage.is_encrypted(): + if not password: + self.log_message( + "ERROR: Wallet is encrypted, please enter password" + ) + return + + try: + storage.decrypt(password) + self.log_message("Wallet decrypted successfully") + except Exception as e: + self.log_message(f"ERROR: Wrong password: {str(e)}") + return + + # Read wallet + data = storage.read() + json_wallet = json.loads("[" + data + "]")[0] + + have_to_save = False + message = "" + + if command == "fix": + have_to_save = fix_will_settings_tx_fees(json_wallet) + message = ( + "Fix applied successfully" if have_to_save else "No fix needed" + ) + + elif command == "uninstall": + have_to_save = uninstall_bal(json_wallet) + message = ( + "BAL uninstalled successfully" + if have_to_save + else "No BAL settings found to uninstall" + ) + + if have_to_save: + try: + save(json_wallet, storage) + self.log_message(f"SUCCESS: {message}") + except Exception as e: + self.log_message(f"Save error: {str(e)}") + else: + self.log_message(f"INFO: {message}") + + except Exception as e: + error_msg = f"ERROR: Processing failed: {str(e)}" + self.log_message(error_msg) + + +def main(): + app = QApplication(sys.argv) + + window = WalletUtilityGUI() + window.show() + + return app.exec() + + +if __name__ == "__main__": + sys.exit(main())