The Building Will report showed a fixed list of three "possible reasons"
whenever a build produced nothing, regardless of what actually happened; in a
case reproduced from the owner log all three were false and the real cause was
not even listed. The "Checking your will" row had the same flaw, showing one
sentence ("Found CHANGES to the DATE or the HEIRS") for five situations,
including one where it is plainly wrong (funds received).
core/heirs.py: record WHY buildTransactions gave up in a new last_build_error
attribute (8 reason codes), set at each path that previously returned empty
with no explanation, plus a processed_willexecutors counter to tell "every
will-executor was skipped" apart from "we tried and failed". Also fix a latent
crash in the prepare_transactions handler, which read a no-longer-existing
e.heirname attribute and re-raised the resulting AttributeError, masking the
real error.
gui/qt/dialogs.py: add msg_alert() (amber warning sign, body text in the theme
colour, readable in both themes), _build_failure_message() and
_check_failure_message() to turn those causes into one precise sentence each,
with an honest "cause could not be determined" fallback. Catch
BalanceTooLowException, which already carried the figures but fell through to
the generic red technical error.
No new exception classes were introduced (owner request): the plain
NotCompleteWillException cases are told apart structurally, not by text.
The checkbox is already available in the Will-Executor tab; showing it
in the settings dialog was redundant. Renumber grid rows 5-16 -> 4-15
to close the gap.
- bal/core/plugin_base.py: change chainname from frozen class attribute
to @classproperty so it reads constants.net.NET_NAME at runtime, fixing
regtest/testnet always downloading the mainnet executor list
- bal/core/willexecutors.py: remove module-level chainname capture;
all uses now read BalPlugin.chainname directly
- bal/gui/qt/dialogs.py: when BalBuildWillDialog detects no heirs,
return 'no_heirs' signal and show Heirs/Wizard/Close buttons (mirroring
the existing no-willexecutor pattern); add HeirsDialog with full
HeirListWidget (New Heir, Import, Export)
Replace the separate Export / Export V-NC toolbar actions with a cascading
Export submenu: All exports every will item, Valid exports only valid ones,
Valid NC exports valid items that are not yet fully signed.
- Sort imports and fix pyproject ruff config (per-file ignores for
intentional Qt/core exceptions)
- Mark Heirs.validate_* helpers as @staticmethod
- Clean up dead code, rename shadowing vars, use raise ... from
- Add AGENTS.md with env/lint/test/release guidance
Allow heirs to produce an OP_RETURN output instead of a regular BTC
payment. An address prefixed with OP_RETURN: carries hex data (max 80
bytes); such heirs always have amount 0 and are excluded from amount
calculations. GUI dialog shows a message field with the decoded text,
the heir list displays the decoded message, and the OP_RETURN output
is emitted with zero value in inheritance transactions.
Read the plugin version at runtime from bal/manifest.json via
importlib.resources (zip-safe, works from inside a zip on Windows).
Removes the four-file duplication (VERSION, __init__.py, plugin_base.py,
manifest.json) and deletes the bal/VERSION file.
- bal/core/plugin_base.py: new get_version() function, BalPlugin.version
is now a @property, removed hardcoded __version__
- bal/__init__.py: removed __version__, replaced with a comment
- bal/core/willexecutors.py: user-agent uses get_version()
- bal/gui/qt/dialogs.py, widgets.py: call sites use .version property
- tests/test_version_source.py: 7 tests incl. zip-safe subprocess check
- HANDOFF.md: updated to single-source-of-truth documentation
- CHANGELOG.md: entries 43-46
- bal/manifest.json: version bumped to 0.6.1
PR #4 (branch bitcoinafterlife-patch-5).
- plugin.py: auto-sign setting con widget nominati, nascosto in basic mode
(visibile solo in ADVANCED, come welist server e calendar app)
- dialogs.py: _on_success_phase1_body() salta firma/broadcast quando
AUTO_SIGN è OFF (solo in advanced; basic usa sempre default=True)
- dialogs.py: _show_next_steps_hint() sopprime hint manuali anche in basic
mode (dove auto-sign è sempre attivo)
- WillExecutorListWidget: double-click/Enter opens edit dialog
- WillExecutorWidget.add() accepts edit_key param for edit mode:
pre-populates URL, Info, Base Fee, Address fields
- Sync button (↻) pings executor URL via TaskThread and populates
fields; updates executor status in list in edit mode (200/KO)
- Loading indicator (⟳) during async ping
- QMessageBox with dialog as parent for sync errors
- Focus returns to URL field after sync error
- 'Add another' button only in add mode, no longer default (OK is default)
- Removed Promo Code field
- Heir dialog: 'Add another' no longer default (matches new pattern)
When deleting old transactions and clicking Check, task_phase1 would fail
with 'NoneType object has no attribute get' error in WillItem.normalize_locktime.
Root cause: In Will.normalize_will (will.py:150), the parameter others_inputs
defaults to None. At line 151, a local variable 'others_input' is created with
a safe default value (empty dict). However, lines 179 and 184 still used the
original 'others_inputs' parameter instead of the safe local variable, causing
the error when calling .get() on None.
Fix: Use the safe 'others_input' variable instead of the raw parameter in
lines 179 and 184.
Also added:
- Traceback logging to on_error_phase1 so future errors include full call stack
- Debug logging in update_all to log willitems state before processing
- Comprehensive test (test_e5_build_with_real_wallet_heirs_and_utxos) that
reproduces the exact scenario from the user's karen7 wallet
Show 'No active will-executor servers for {chain}' when the server
responds with no data, and 'Could not reach the configured welist
server' only for actual connection errors.
- Move User Type combo to row 5 (before Number of reminders)
- Use QLayout.SizeConstraint.SetFixedSize so dialog auto-resizes on mode switch
- Hide event summary, description and reminders count in basic mode, use defaults
- Increase Event Description height to 3 lines
- Set minimum width on line edits for proper dialog sizing in advanced mode
User Type help now says BASIC is safe for most users and lists
welist server URL config + always-editable fields as advanced features.
Welist server help text updated to 'Only available in ADVANCED mode'.
In advanced mode the locktime, check-alive and fees fields should always
be editable regardless of the EDITABLE_DATES setting. Only basic mode
respects that setting.
Raise NoServersForChainError when the welist server responds but returns
no data for the requested chain, showing a chain-specific message instead
of the generic network error.
- #04: Raw/Date selector now reappears on the WILL/HEIR tabs when switching to
ADVANCED (and when a raw value is pushed from the wizard), keeping the current
value/editor. New BalTimeEditWidget.apply_user_type_visibility() wired into
WillSettingsWidget for both the locktime and check-alive boxes.
- #3: Building Will report window minimum height 500 -> 450 px.
- #5: 'User Type' setting moved to the bottom, above 'Rebroadcast transactions'.
- #6: enabling ADVANCED now requires typing 'at My Risk' (case-insensitive);
wrong phrase or cancel reverts to BASIC. QInputDialog added to common import.
- #7a: on CHECK with an emptied wallet, an extra reassuring line is shown on
'Checking your will': 'Inheritance already executed (on blockchain)' in GREEN
(CONFIRMED) or 'Inheritance in mempool (waiting confirmation)' in ORANGE
(MEMPOOL). New _executed_inheritance_status() helper.
- #7b: 'Balance is too low, or CheckAlive is in the past. Skipped' (space added)
recoloured to ORANGE instead of red.
- Reset button renamed 'Reset setting' -> 'Reset to Default Setting'.
- Added tests/test_group_h_v048.py (8 tests). Version 0.4.7 -> 0.4.8.
Full suite: 266 passed; ruff clean; CHANGELOG #24; memory updated (#04 done).
Owner-approved changes after testing v0.4.6, plus accumulated v0.4.x work,
task-tracking notes, and an updated project HANDOFF document.
The four v0.4.7 changes:
1. Report area (BalBuildWillDialog) opens 500px tall (min) up to 700px (max),
then the scrollbar takes over. Previously it opened ~140px (too short).
2. Heirs are listed ONE per line again (green, bold) in _build_success_report,
reverting the v0.4.6 single-line form. Heir names can be long and the report
now scrolls, so compression is no longer needed.
3. Two wizard texts get an explicit line break: after "(or backup)" in the date
hint and after "miner fees" in the fee note (widgets.py).
4. ALL-DUST guard: when EVERY heir's share is below the Bitcoin dust limit, the
inheritance would pay nobody. Heirs.prepare_lists now raises
HeirAmountIsDustException at the end (where all heirs across all locktimes
are known with their final dust state), and dialogs.task_phase1 shows a clear
RED message and stops without building/signing/checking. A mix of dust +
valid heirs keeps building normally. The guard is intentionally in
prepare_lists, NOT prepare_transactions (which only sees the lowest locktime
and would false-positive). HeirAmountIsDustException is imported in common.py.
Tests: 3 new tests in test_core_heirs_extra.py pin the dust behaviour (all-dust
raises; mixed continues; multi-locktime continues). Full suite: 258 passed.
ruff: no new errors. Version bumped 0.4.6 -> 0.4.7 (4 files). CHANGELOG #23.
Also adds/updates HANDOFF.md so any future AI (Claude or another model) can
resume the project with full context (rules, layout, build/test/lint, dust
logic, git flow), and records the task-tracking notes in
.agent_memory_tasks.md.
UI batch (TASK A/B/C/D):
- (A) Clearer 'Will expired' message: shortened will id (8+8 chars) and
readable UTC date instead of raw UNIX timestamp; shown in WARNING colour
(orange) instead of ERROR (red) in the wizard, as it is an expected step.
- (A2) Split the orange message onto two lines via <br> (rendered as HTML by
msg_warning) to avoid an overly long single line.
- (B) History tab labels (text only): inheritance txs -> 'BAL Inheritance
transaction'; invalidate tx -> 'BAL Invalidate transaction'. Colours left
to Electrum defaults.
- (C) New 'No will-executor TX' checkbox in plugin settings, bound to the
existing NO_WILLEXECUTOR config (default ON, kept in sync with the wizard),
with help text, and included in the settings reset action.
- (D) Wizard button label 'Create your will' -> 'Build Your Will'.
Regression fix (A3):
- When an heir was added to an already-expired will via the wizard, the
automatic invalidation no longer triggered. Root cause: the inner
check_will() after build_will() raised WillExpiredException and the handler
returned (False, None), which never invalidated.
- Auto-opening the invalidation tx window from the closing wizard proved
unreliable (the window ended up behind the main wallet on some window
managers; a re-check loop could ask to invalidate repeatedly before the tx
reached the mempool). The robust fix: close the wizard and show a clear
instruction popup guiding the user to run 'Tools -> Invalidate' and then
press 'Check' to finish the will. Reasoning documented in the code.
Version bumped to 0.3.9 (manifest.json, __init__.py, plugin_base.py, VERSION).
CHANGELOG entry #15 updated. Verified: py_compile OK, ruff clean (no new
errors), full test suite 239 passed.
Rewrite the CHECK ALIVE help popup (ThresholdTimeWidget.help_text) to explain
both modes, keeping the <b>CHECK ALIVE</b> title bold:
- DATA mode: set the check-alive date; on wallet open, if the date has passed
the plugin asks whether to postpone the inheritance.
- RAW mode: when less than this time is missing, ask to invalidate; failing to
invalidate delivers the transactions to the heirs; kept the d/y suffix help.
Fix two typos within this help text: 'less then' -> 'less than',
'currrent' -> 'current'.
Bump version to 0.3.8 across the four version files. CHANGELOG entry #14.
- open_or_save_calendar(): emit one VEVENT per reminder instead of a single
event with VALARM blocks. Dates come from compute_reminder_offsets(), spread
across the check-alive period, with the LAST event one day before the
locktime. Unique UID per event (bal-<wallet>-<offset>d) and numbered
summaries ' (reminder N/total)'.
- Removed the now-unused create_alarms() method (no more VALARM).
- Default save filename changed from will_event.ics to BAL_will_event.ics.
- common.py: added timedelta to the datetime import.
- plugin_base.py: updated the NUM_REMINDERS comment for the new behaviour.
- tests: replaced the VALARM E1 test with test_e1_build_separate_events_for_giovanna.
- Bumped version to 0.3.7 (4 files) and added CHANGELOG entry #13.
Tests: 239 passed. ruff: no new errors.
Two GUI fixes bundled together (v0.3.6):
- Fix#10 (black bar): in dialogs.py on_success_phase1, the cancelled
invalidation branch no longer calls the blocking self.wait(3)+self.close().
wait() uses time.sleep() and runs in the GUI thread, freezing the UI so the
'Building Will' dialog could not repaint the area it just resized, leaving a
black rectangle at the bottom. It now shows 'Aborted' plus the existing
non-blocking Close button (_add_close_button), consistent with the other
end-of-flow branches.
- Fix#11 (editable fee): in widgets.py WillSettingsWidget.apply_editable_dates,
the fee widget (baltx_fees) now follows the same 'Editable dates' setting as
the locktime/threshold widgets (set_read_only(not editable_dates)) instead of
being forced read-only. With the setting ticked, dates AND fee are editable
outside the wizard; unticked, all three are read-only. Default stays OFF.
Version bumped 0.3.5 -> 0.3.6 (manifest.json, __init__.py, plugin_base.py,
VERSION). CHANGELOG: entries #10 and #11. ruff clean; full suite 239 passed.
D1 - number of reminders (default 3, max 5):
- new NUM_REMINDERS config in plugin_base.py
- new BalSpinBox widget bound to a BalConfig
- new pure helper compute_reminder_offsets(days, count): reminders spread
uniformly across the check-alive period, always before the deadline
(offset >= 1), at most one per available day, de-duplicated, earliest
first (e.g. (30,3)->[30,16,1], (2,3)->[2,1], (1,3)->[1], (0,3)->[])
- create_alarms() rewritten to use it and emit one VALARM per offset with a
DESCRIPTION reminder text
- 'Number of reminders' spin box (range 1..5) added to the settings dialog
and to the Reset list
D1b - save-only .ics (ask where, default to Desktop):
- BalCalendar.desktop_dir() helper (~/Desktop with home fallback)
- open_or_save_calendar() no longer opens the file with a calendar app; it
always shows a 'save as' dialog (.ics filter, default will_event.ics,
starting on the Desktop) and copies the file there, then confirms.
Identical behaviour on Windows/Linux/macOS. save_to_cwd -> save_ics_to.
Follow-up: removed the now-unused 'Calendar App' field from the settings
dialog (and from the Reset list). CALENDAR_APP config and
open_with_default_app left in place (unused) to avoid unrelated changes.
D2 intentionally skipped (per user request).
tests/test_group_d_alarms.py: 7 new tests (NUM_REMINDERS default/change and
all distribution rules). Full suite: 217 passed.
C2 - 'Editable dates' option (default OFF):
- new EDITABLE_DATES config; checkbox in settings dialog
- WillSettingsWidget.apply_editable_dates() re-locks/unlocks the
delivery-time and check-alive fields, called from update_all() so the
toggle takes effect immediately (same mechanism as Hide Invalidated)
- EDITABLE_DATES included in the Reset-to-defaults list
C3 - RAW date box narrowed to ~1/3 (input box 6 chars); the trailing
label that shows the computed absolute date kept at 10 chars (fixed a
truncation regression)
C4 - settings dialog:
(a) bold red warning at the top
(b) 'Reset setting' button restoring all 7 dialog settings to defaults
(single source of truth: BalConfig.default), not touching wills
(c) bold 'Support: bitcoin-after.life' link (opens via webopen)
C5 - tooltips: calendar 'Export reminder dates to your calendar (.ics)',
fee field 'Mining fee rate in sat/vByte used for the will transactions',
fee '丰' icon 'Miner fee, click for more information' (tooltip font scoped
to QPushButton so it matches the other tooltips)
C6 - Locktime column rendered in bold in the will list
tests/test_group_c_settings.py: 4 new tests (EDITABLE_DATES default/toggle,
reset restores all dialog settings incl. EDITABLE_DATES, reset leaves
unrelated config untouched). Full suite: 210 passed.
Bump plugin version to 0.3.4 (manifest, __init__, plugin_base, VERSION).
GROUP A
- A1: remove block-height locktimes; the plugin now uses UNIX timestamps
only. The NLOCKTIME_BLOCKHEIGHT_MAX guard is kept on purpose (it forces
every locktime to be a timestamp). chk_locktime is now 2-arg; int_locktime
and anticipate_locktime no longer accept blocks; RAW input only accepts d/y.
Two now-dormant configs (LOCKTIME_BLOCKS, LOCKTIMEDELTA_BLOCKS) are kept with
comments to avoid touching persisted keys.
- A2: rename PENDING -> MEMPOOL everywhere (label 'Mempool', yellow #ffce30);
add new UPDATED status; ANTICIPATED & UPDATED keep VALID; documented
set_status rules; backward-compat migration (old PENDING -> MEMPOOL).
- A3: clarify that anticipating to a future date only rebuilds (never
invalidates), while only a past locktime invalidates (WillExpired). Code was
already correct; only the comment and docs were fixed.
Colour follow-up: UPDATED lightened from #800080 to #b266b2 (more readable),
updated in theme.py, docs and the theme test.
GROUP B
- B1: verified the 'Create your will' button already opens the guided wizard
(no code change needed).
- B2: new persisted AUTO_SIGN setting (default ON) with an 'Auto-sign on Check'
checkbox in the settings dialog. When enabled, Check signs and broadcasts
automatically; the wallet password is requested only for encrypted wallets.
B2 follow-up (fixes reported after testing):
- Remove the duplicate sign/broadcast cycle in lists.check(); build_will_task()
already signs and broadcasts.
- Suppress the manual 'press Sign/Broadcast' hint and its popup when AUTO_SIGN
is ON (kept when OFF).
- Make broadcast one-shot: removed the retry flag and the Exception('retry');
failed will-executors stay PUSH_FAIL and are skipped (no endless retry).
PUSHED transactions are already excluded from re-collection.
Docs: inheritance-options.md/.html and inheritance-flow.svg updated to v0.3.4.
Tests: 206 passing (new test_anticipate_manual_locktime, test_anticipate_past_locktime,
test_group_b_auto_sign; updated core/util, core/will_extra, gui/theme, gui/widgets).
CHANGELOG.md added with one numbered entry per task.
Fix and refine the BAL plugin GUI without changing business logic:
core/will.py: restore the PUSHED requirement in needs_server_check so a
signed-but-not-broadcast will is no longer server-queried and therefore
stays blue (COMPLETE) instead of turning red (CHECK_FAIL). This matches the
original Gitea check() condition.
gui/qt/widgets.py: WillSettingsWidget vertical layout now caps every row to
the widest date-row width and left-aligns them; the leading icons keep their
original HelpButton width.
gui/qt/lists.py + gui/qt/common.py: the wizard toolbar button now shows a
28x28 icon plus a bold 'Create your will' caption (QSize imported).
gui/qt/dialogs.py (BalBuildWillDialog):
- closing summary row labelled 'All done: Ok' with a blank separator above it;
- 'checking variables' capitalised to 'Checking variables' (redundant trailing
colon dropped);
- final auto-closing countdown replaced by an explicit right-aligned 'Close'
button; intermediate technical pauses kept; next-steps popup preserved.
gui/qt/window.py + core/plugin_base.py: guide show_message on build, and
sync_hide_filters() in update_all so hide flags refresh immediately.
tests: test_needs_server_check updated; added offscreen preview helpers.
Version bumped to 0.3.3. 186 tests pass; ruff clean (baseline only).
Revert the v0.3.1 double-invalidation change (it caused an inheritance-list
regression: stale/invalidated wills lingered and heir/date updates became
incoherent) and add several targeted missed-update fixes plus a UI refinement.
Revert (v0.3.1 -> v0.3.2):
- Remove Will.mark_invalidated_by_tx() and its call in
loop_broadcast_invalidating. core/will.py and gui/qt/dialogs.py are restored
to the working v0.3.0 behaviour. The postpone double-invalidation issue is
intentionally left open, to be addressed without touching the shared
broadcast path.
FIX 1 - detect heir removal on Check / Electrum close:
- core/will.py (check_willexecutors_and_heirs): the else-branch now raises
HeirNotFoundException when a will still carries an heir that is no longer in
the current heirs set (heir removed), mirroring the existing 'heir added'
path. Rebuild therefore triggers on Check and on_close (same build_will_task
path), as decided by the user (manual update only, no auto-rebuild).
FIX 2 - Check queries servers for already-sent wills:
- core/will.py: new Will.needs_server_check(w) returns True for any VALID will
with a will-executor that is not yet CHECKED (no longer limited to PUSHED).
- gui/qt/lists.py (PreviewList.check): use needs_server_check so wills stuck on
'New / Not sent' are re-checked instead of reporting 'nothing to do'.
FIX 3 - Settings-dialog hide toggles refresh the list:
- core/plugin_base.py: new sync_hide_filters() re-reads the cached
_hide_invalidated / _hide_replaced flags from the persisted config.
- gui/qt/window.py (update_all): call sync_hide_filters() before refreshing, so
toggling 'Hide Invalidated' / 'Hide Replaced' in the Settings dialog (which
writes the config directly) updates the transaction list immediately instead
of requiring an Electrum restart.
UI - bold results in the Building Will dialog:
- gui/qt/dialogs.py (BalBuildWillDialog): render the right-side results in bold
(Ok, Ko, Nothing to do, Skipped, Wait, Timeout, ...) keeping the left-side
state labels in normal weight. Centralised in msg_ok/msg_error/msg_warning/
msg_set_status, plus the will-executor push/check rows now show Ok/Ko and
True/False in bold + colour (green/red).
Tests/tooling:
- tests/test_core_will.py: add test_check_heirs_unchanged_is_coherent,
test_check_heir_removed_triggers_rebuild, test_check_heir_added_triggers_rebuild,
test_needs_server_check.
- tests/sim_update_flows.py: real-world update-scenario simulation.
- tests/preview_build_will_dialog.py, tests/preview_we_rows.py: GUI-only
before/after previews of the bold formatting.
- Bump version to 0.3.2 (VERSION, manifest.json, __init__.py, plugin_base.py).
186 tests pass; smoke test, external-zip test and update-flow simulation OK;
ruff reports only pre-existing star-import false positives.
When postponing the delivery time of an already signed/sent will, the user
was asked to sign the on-chain invalidation transaction twice before the new
(postponed) will could be built.
Root cause: after the invalidation tx was broadcast, on_success_invalidate
restarted task_phase1 to rebuild the will, but the old will items were still
marked COMPLETE/PUSHED with their original tx.locktime (the on-chain
invalidation did not update the in-memory status). The postpone check therefore
fired WillPostponedException a second time, requesting another invalidation.
Fix:
- Add Will.mark_invalidated_by_tx(will, tx): marks INVALIDATED every valid will
item that spends a prevout consumed by the just-broadcast invalidation tx.
Setting INVALIDATED clears the VALID flag, removing those items from
only_valid_list so the postpone/expire check no longer fires.
- Call it from loop_broadcast_invalidating after a successful broadcast (txid
obtained) and persist via save_willitems. On the phase-1 restart the old will
is no longer VALID, so the will is rebuilt directly: a single invalidation
signature followed by the new will.
Tests: add test_will_mark_invalidated_by_tx and
test_will_mark_invalidated_by_tx_no_match plus the WillPostponedException
hierarchy assertion. 184 tests pass; smoke and external-zip OK; ruff clean.
Bump version to 0.3.1.