forked from bitcoinafterlife/bal-electrum-plugin
add wallet_util
This commit is contained in:
50
bal/wallet_util/README.md
Normal file
50
bal/wallet_util/README.md
Normal file
@@ -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 <same arguments>
|
||||||
|
```
|
||||||
|
- 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.
|
||||||
81
bal/wallet_util/bal_wallet_utils.py
Executable file
81
bal/wallet_util/bal_wallet_utils.py
Executable file
@@ -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 <command> <wallet path>")
|
||||||
|
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")
|
||||||
199
bal/wallet_util/bal_wallet_utils_qt.py
Executable file
199
bal/wallet_util/bal_wallet_utils_qt.py
Executable file
@@ -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())
|
||||||
Reference in New Issue
Block a user