add bal root files

This commit is contained in:
bot
2026-06-20 09:48:50 -04:00
parent 70f76b42d1
commit b9c00446c4
7 changed files with 189 additions and 0 deletions

21
bal/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 copronista
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

23
bal/README.md Normal file
View File

@@ -0,0 +1,23 @@
# BalPlugin
Bitcoin After Life Electrum Plugin
Free and decentralized Bitcoin inheritance support for Electrum: build
time-locked "will" transactions that transfer your funds to your heirs if you
stop refreshing them (dead-man's switch), optionally relayed by will-executor
servers.
## Key behaviours
- **Anticipate / postpone safety**: changing the delivery time of an
already-signed will is handled safely. Postponing a signed/sent will first
asks you to invalidate the old transaction on-chain (so a will-executor can
never broadcast the earlier-locktime transaction and execute the inheritance
too early), then lets you rebuild and re-send the new one via
**Tools → Prepare**.
- **"Server" column**: the will transaction list shows whether each transaction
is actually stored on the will-executor servers
(`Confirmed on server`, `Sent (not checked)`, `Send failed`,
`Not on server`, `Signed (not sent)`, `Not sent`), with a tooltip showing the
will-executor URL.
See the top-level [`README.md`](../README.md) for installation and testing.

1
bal/VERSION Normal file
View File

@@ -0,0 +1 @@
0.3.3

37
bal/__init__.py Normal file
View File

@@ -0,0 +1,37 @@
"""BAL - Bitcoin After Life Electrum plugin.
Free and decentralized Bitcoin inheritance support for the Electrum wallet.
This package was reorganized (Approach A: conservative, behavior-preserving)
to cleanly separate logic from presentation. The original monolithic plugin
mixed the business logic with the PyQt GUI; here the two concerns live in
distinct sub-packages:
bal/
core/ GUI-free business logic (importable without Qt)
util.py Generic helpers (encoding, validation, ...)
plugin_base.py BasePlugin subclass, config, timestamp handling
heirs.py Heir list model + transaction building
will.py Will / WillItem domain model
willexecutors.py Will-executor (dead-man's switch) networking
gui/
qt/ PyQt6 presentation layer
theme.py Colors / status -> color mapping (status_color)
common.py Shared imports and small GUI helpers
widgets.py Leaf widgets (editors, labels, checkboxes, ...)
calendar.py BalCalendar widget
dialogs.py Dialog windows (wizard, build-will, detail, ...)
lists.py Tree/list views (heirs, preview, will-executors)
window.py BalWindow controller (per-wallet GUI state)
plugin.py Plugin class wiring Electrum @hooks to the GUI
qt.py Thin loader shim re-exporting `Plugin` for Electrum
Electrum discovers the plugin through ``manifest.json`` and loads the GUI
entry point from ``qt.py`` (the shim), which imports the real ``Plugin``
from ``gui.qt.plugin``.
The plugin targets Electrum 4.7.2 (the last stable release exposing
``json_db.register_dict``) and PyQt6.
"""
__version__ = "0.3.3"

14
bal/bal_resources.py Normal file
View File

@@ -0,0 +1,14 @@
import os
PLUGIN_DIR = os.path.split(os.path.realpath(__file__))[0]
DEFAULT_ICON = "bal32x32.png"
DEFAULT_ICON_PATH = "icons"
def icon_path(icon_basename: str = DEFAULT_ICON):
path = resource_path(DEFAULT_ICON_PATH, icon_basename)
return path
def resource_path(*parts):
return os.path.join(PLUGIN_DIR, *parts)

10
bal/manifest.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "bal",
"fullname": "Bitcoin After Life",
"version": "0.3.3",
"description": "Provides free and decentralized Bitcoin inheritance support. Build time-locked 'will' transactions that transfer funds to your heirs if you stop refreshing them (dead-man's switch), optionally relayed by will-executor servers.",
"author": "Svatantrya",
"licence": "MIT",
"available_for": ["qt"],
"icon": "icons/bal32x32.png"
}

83
bal/qt.py Normal file
View File

@@ -0,0 +1,83 @@
"""
bal.qt
======
Compatibility shim for Electrum's plugin loader.
Electrum loads a Qt plugin by importing the ``qt`` module of the plugin package
and looking for a ``Plugin`` class. The real implementation lives in the
well-separated ``bal.gui.qt`` sub-package, so this module re-exports the
``Plugin`` class from ``bal.gui.qt.plugin``.
Why this file is not a one-line relative import
-----------------------------------------------
A plain ``from .gui.qt.plugin import Plugin`` works fine when the plugin is
installed as an *internal* plugin (under ``electrum/plugins/bal``). However,
when the very same code is loaded as an *external* plugin from a ``.zip``,
Electrum 4.7.x imports the package under the synthetic top-level name
``electrum_external_plugins.bal`` and only executes the package ``__init__`` and
this ``qt`` module. It never registers the intermediate parent packages
(``electrum_external_plugins`` itself, ``...bal.gui``, ``...bal.gui.qt``). As a
result, a relative import that has to walk up to those parents fails with::
ModuleNotFoundError: No module named 'electrum_external_plugins'
To make the plugin work *both* as an internal package and as an external zip,
this shim resolves and imports ``Plugin`` defensively:
1. It works out the name of the package this module lives in
(``__package__``), whatever Electrum decided to call it.
2. It makes sure every parent package in that chain exists in
``sys.modules`` so Python's import machinery can resolve sub-modules.
3. It imports the ``.gui.qt.plugin`` sub-module via :func:`importlib.import_module`
using the resolved absolute name.
This keeps the clean ``core`` / ``gui`` layout while staying robust to how the
plugin is loaded.
"""
import importlib
import sys
def _ensure_parent_packages(pkg_name: str) -> None:
"""Make sure every ancestor package of *pkg_name* is in ``sys.modules``.
When loaded from a zip as an external plugin, Electrum only executes the
plugin package ``__init__`` and the ``qt`` module. The synthetic root
package (e.g. ``electrum_external_plugins``) and any intermediate packages
may be missing from ``sys.modules``, which breaks relative/absolute
sub-module imports. We backfill them here using this module's own loader
so that ``importlib`` can find sibling sub-packages.
"""
parts = pkg_name.split(".")
# Walk from the top-most ancestor down to (but not including) pkg_name.
for i in range(1, len(parts)):
ancestor = ".".join(parts[:i])
if ancestor in sys.modules:
continue
try:
importlib.import_module(ancestor)
except Exception:
# The synthetic root (e.g. 'electrum_external_plugins') often has no
# real spec. Create a minimal namespace package stub so that the
# import machinery can still resolve its children.
import types
module = types.ModuleType(ancestor)
module.__path__ = [] # mark as a (namespace) package
sys.modules[ancestor] = module
# The package this module belongs to. Could be 'electrum.plugins.bal' (internal)
# or 'electrum_external_plugins.bal' (external zip), depending on how Electrum
# loaded us.
_PKG = __package__ or "bal"
_ensure_parent_packages(_PKG)
# Import the real implementation using the fully-qualified, run-time package
# name so it works regardless of the synthetic prefix Electrum assigned.
_plugin_module = importlib.import_module(_PKG + ".gui.qt.plugin")
Plugin = _plugin_module.Plugin # noqa: F401 (re-exported for Electrum)