Adds a single handoff document summarizing the mandatory rules (R1-R4, zip-first), project overview, build/test/release commands, current state (v0.3.9 released), the pending 'unify invalidate procedure' task with the user-approved warning popup text, and a key file/line map. Lets any new chat or AI model resume work without losing context. Documentation only; no plugin code changed.
11 KiB
HANDOFF — BAL Electrum Plugin (Bitcoin After Life)
Purpose of this file: allow ANY new chat / AI model to resume this project WITHOUT losing context. If you are a new assistant, READ THIS FILE FIRST, then read
CHANGELOG.mdand.agent_memory_tasks.md.Chat language is Italian, but ALL output (code, comments, UI text, docs, CHANGELOG, commit messages) MUST be in ENGLISH. The user is NOT a programmer.
1. MANDATORY STANDING RULES (apply to EVERY task, never skip)
- R1 — LANGUAGE: Italian is ONLY for chatting. ALL deliverables in ENGLISH (code, docstrings, comments, UI strings, docs, CHANGELOG, commit messages).
- R2 — DOCUMENTED CODE: every method/class needs a docstring + explanatory comments. When a non-obvious design choice is made, explain WHY in a comment.
- R3 — NEVER INVENT: if anything is missing or unclear, STOP and ask the user clear, simple questions (he is not a programmer). Be "100% sure" before acting.
- R4 — HUMAN CHECKPOINT: before writing/modifying code, show the PLAN and WAIT for the user's explicit "OK".
- METHOD per task: DISCOVER → PLAN (wait OK) → EXECUTE → VERIFY → ITERATE (max 8 attempts, then declare "UNRESOLVED").
- LOG: a single
CHANGELOG.mdin English, one numbered entry per task. - ZIP-FIRST: always deliver a test ZIP for the user to try BEFORE committing plugin code. Commit ONLY after explicit user confirmation.
- Always run
ruff+ the official test suite before committing/reporting/zipping.
2. PROJECT OVERVIEW
- Electrum 4.7.2 Qt (PyQt6) inheritance plugin.
- External zip plugin id:
electrum_external_plugins.bal. - zipimport caches the plugin → the user MUST fully restart Electrum after installing a new ZIP (always remind him).
- Repo:
Bitcoin-after-life/test(GitHub). Working branch:genspark_ai_developer. - Main code lives under
bal/. Tests undertests/. Electrum source vendored inelectrum-src/(read-only reference).
Version files (keep ALL FOUR in sync on every release)
bal/manifest.json→"version"bal/__init__.py→__version__bal/core/plugin_base.py→__version__ = "..." # AUTOMATICALLY GENERATED DO NOT EDITbal/VERSION- Current released version: 0.3.9
3. BUILD / TEST / RELEASE COMMANDS
Build the ZIP (clear caches first)
cd /home/user/webapp
find bal -name "__pycache__" -type d -exec rm -rf {} + ; find bal -name "*.pyc" -delete
python3 build_zip.py bal-electrum-plugin-vX.Y.Z.zip # builds 37 files
Run the full test suite (must stay GREEN: 239 passed)
cd /home/user/webapp
QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src python3 -m pytest \
tests/test_core_*.py tests/test_gui_*.py \
tests/test_anticipate_past_locktime.py tests/test_anticipate_manual_locktime.py \
tests/test_group_b_auto_sign.py tests/test_group_c_settings.py \
tests/test_group_d_alarms.py tests/test_group_e_mock_giovanna7.py -q
Ruff (only PRE-EXISTING noise is acceptable)
cd /home/user/webapp && ruff check bal/<changed files>
Pre-existing warnings that are NOT your fault and can be ignored:
F401/F403/F405star-import noise (from .common import *).F841at:will.py:151,dialogs.py:615(except NoHeirsException as e),lists.py:185,lists.py:272.E501long lines in several pre-existing spots.
Git / PR / Release workflow
setup_github_environmentfirst. Ifgit pushfails with "Invalid username or token", CALLsetup_github_environmentAGAIN, then retry.- Token for API calls:
TOKEN=$(sed -n 's#https://\([^:]*\):\([^@]*\)@.*#\2#p' ~/.git-credentials | head -1) - Repo for API:
Bitcoin-after-life/test. - Releases published so far: v0.3.6, v0.3.7, v0.3.8, v0.3.9 (latest).
- Attach the ZIP as a release asset via the uploads API.
4. CURRENT STATE (as of v0.3.9, COMMITTED + RELEASED)
v0.3.9 is merged to main (PR #11) and released with the ZIP attached.
sha256 of the released ZIP: cd52b6f5e6276fb707ea4bf477a00bd0469dfd88960282fb74c219ee0f5f4292.
What shipped in v0.3.9 (TASK A/B/C/D + regression fix A3):
- A clearer "Will expired" message: shortened will id (8+8 chars via
Will._short_will_id) + readable UTC date (Will._format_locktime) instead of raw UNIX timestamp; shown ORANGE (warning) not RED (error) in the wizard. - A2 message split on two lines via
<br>(rendered as HTML bymsg_warning). - B History labels (text only): inheritance tx →
BAL Inheritance transaction; invalidate tx →BAL Invalidate transaction. Colours = Electrum defaults (Electrum colours outgoing tx descriptions red by itself, history_list.py:193-196). - C "No will-executor TX" checkbox in plugin Settings (
plugin.py), bound to existingNO_WILLEXECUTORconfig (default ON, line plugin_base.py:193), with help text "Create a will that does not require a Will-executor; it can be saved, for example, on a USB stick, and a copy can be given to the heirs." Included in reset. - D wizard button "Create your will" → "Build Your Will" (lists.py:473).
- A3 regression fix: adding an heir to an expired will via the wizard no longer
failed to invalidate. Implemented a
"invalidate_classic"signal returned fromtask_phase1and handled inon_success_phase1(dialogs.py) that closes the wizard and shows a popup telling the user to useTools → Invalidate. NOTE: this popup text is about to be CHANGED — see the pending task below.
5. PENDING TASK (NOT STARTED) — UNIFY THE INVALIDATE PROCEDURE
Problem the user reported
There are currently TWO different "invalidate" procedures, and the user wants ONE identical behaviour for BOTH the CHECK button and the WIZARD.
- PROCEDURE 1 "classic/manual" =
window.py::invalidate_will(line ~695): waiting dialog → "please sign and broadcast" popup → CLASSIC Electrum tx window (Sign/Broadcast buttons) → SETS history label "BAL Invalidate transaction" (line ~704). Used by: Tools→Invalidate menu (lists.py:468→571), a dialog button (dialogs.py:1356), the on-close/postpone paths (window.py:539,576,614). This window opens correctly IN FRONT (user confirmed) because nothing else is closing at the same time. - PROCEDURE 2 "automatic" =
dialogs.py::invalidate_task(line ~902): password prompt inside the wizard → sign + auto-broadcast (loop_broadcast_invalidating, line ~729) → does NOT set the history label. Used by the CHECK button and the FIRSTWillExpiredExceptionhandler intask_phase1(dialogs.py:594 →return None, Will.invalidate_willat ~598, which makeson_success_phase1seehave_to_sign is None→ password prompt).
CHECK button flow (important)
lists.py:545 check() → BalBuildWillDialog(...).build_will_task() →
task_phase1 → on_success_phase1. It is the SAME engine as the wizard.
FINAL REQUIREMENT (user-approved, OPTION A refined)
Make CHECK and WIZARD behave IDENTICALLY for an expired will:
- First show a WARNING popup (REMOVE the old "use the top-right menu Tools → Invalidate" wording).
- Then AUTOMATICALLY open the CLASSIC Electrum sign window (PROCEDURE 1,
window.py::invalidate_will) so the label is set and the user can Sign + Broadcast. - Sequence: warning popup → user clicks OK → classic sign window opens BY ITSELF, IN FRONT.
APPROVED WARNING POPUP TEXT (verbatim, English per R1)
Your will has expired and must be invalidated before it can be rebuilt.
A transaction window will now open:
please SIGN and then BROADCAST it to invalidate your old will.
After the invalidation is confirmed, press the Check button to finish the will.
(The user wrote "SIGN and then BROADCAST" with a double space — normalize to a single space unless he objects.)
Implementation approach (agreed in principle; still needs final PLAN + OK)
- Route the expired cases (FIRST handler at ~594, the
invalidate_classicblock, and therefore the CHECK button) through ONE shared helper that: (a) closes the CHECK/wizard dialog FIRST, (b) shows the warning popup, (c) then callsself.bal_window.invalidate_will()(PROCEDURE 1) LAST, so the classic window is the last thing opened and stays in front. - Drop the use of PROCEDURE 2 (
invalidate_task) for the expired case. - KNOWN RISK / why this is delicate: earlier attempts to auto-open the classic
window while the wizard was closing put it BEHIND the main wallet window on the
user's machine (Windows focus/stacking). The fix is to make sure NOTHING closes
AFTER the classic window opens (close the dialog first, open the tx window last).
Tools → Invalidateworks perfectly precisely because no other window is closing. - The user has hinted he may add MORE requirements before this is implemented, so CONFIRM the full scope before coding.
Status: WAITING. Do NOT code yet. Build full PLAN → wait OK (R4) → zip-first.
6. KEY FILE / LINE REFERENCES (verify line numbers, they drift)
bal/core/will.pycheck_will()order (line ~561):check_invalidated→check_will_expired(raisesWillExpiredException) →search_rai(raisesHeirNotFoundException).invalidate_will()static (line ~394) builds the invalidation PartialTransaction._short_will_id/_format_locktimehelpers + the expired message (with<br>).
bal/gui/qt/dialogs.pyBalBuildWillDialogis the CHECK + wizard engine.build_will_task()(~530) startstask_phase1.task_phase1()(~542): firstcheck_will(); firstWillExpiredExceptionhandler (~594) →return None, Will.invalidate_will(...);NotCompleteWill/HeirNotFound→have_to_build; innercheck_will(); innerWillExpiredException(~659) → currently returns"invalidate_classic", None.on_success_phase1()(~924): unpacks(have_to_sign, tx). Ifhave_to_sign == "invalidate_classic"→ shows popup. Ifhave_to_sign is None→ password prompt "Invalidate your old will" →invalidate_task(PROCEDURE 2).invalidate_task()(~902) +loop_broadcast_invalidating()(~729): PROCEDURE 2.QTimeris available viafrom .common import *(defined in common.py:53).
bal/gui/qt/window.pyinvalidate_will()(~695): PROCEDURE 1 (the "good" one). Sets label at ~704.show_transaction_real()(~656) usesshow_on_top(d, modal_to_window=False).- on-close/postpone expired handling at ~539, ~576, ~614.
bal/gui/qt/lists.pycheck()(~545): the CHECK button.invalidate_will()(~571). Menu actions "Check"/"Invalidate" at ~467/468. Wizard button "Build Your Will" at ~473.
bal/gui/qt/plugin.py: settings dialog; "No will-executor TX" checkbox + reset.bal/gui/qt/window_utils.py:show_on_top(~100),bring_to_front(~52),show_modal(~86).bal/gui/qt/common.py:add_widgethelper (~98); importsQTimer,Qt, etc.
7. HOW TO RESUME IN A NEW CHAT (any model)
Tell the new assistant:
"Read
/home/user/webapp/HANDOFF.md, thenCHANGELOG.mdand.agent_memory_tasks.md. Follow rules R1–R4 and zip-first. The next task is the 'unify invalidate procedure' task in HANDOFF.md section 5 — present the PLAN and wait for my OK before coding."
Everything needed (rules, state, pending task, build/test commands, file map) is in this file. The real work is safe in Git (PR #11, release v0.3.9) and in CHANGELOG.md.