core+gui: timezone-correct datetimes, deep-copy WillItem, dead code removal, explicit imports
This commit is contained in:
28
AGENTS.md
28
AGENTS.md
@@ -20,8 +20,9 @@ The plugin's `bal/` directory is symlinked into
|
||||
|
||||
## Test & verify
|
||||
|
||||
Tests are **standalone scripts**, not pytest. Each `tests/test_*.py` file runs
|
||||
its `test_*` functions from `if __name__ == "__main__"`. Run a file directly:
|
||||
Tests work **both** as standalone scripts and via pytest (tests use `def test_*`
|
||||
naming and also have `if __name__ == "__main__"` blocks). Run a single file
|
||||
directly:
|
||||
|
||||
```bash
|
||||
source /home/steal/devel/bal/electrum/env/bin/activate
|
||||
@@ -29,6 +30,13 @@ python3 tests/test_core_heirs.py # core, no Qt needed
|
||||
QT_QPA_PLATFORM=offscreen python3 tests/test_gui_common.py # GUI tests need offscreen
|
||||
```
|
||||
|
||||
Or run a batch with pytest (as `make-release.sh` does):
|
||||
|
||||
```bash
|
||||
source /home/steal/devel/bal/electrum/env/bin/activate
|
||||
QT_QPA_PLATFORM=offscreen python3 -m pytest tests/test_core_*.py -q
|
||||
```
|
||||
|
||||
- Most core tests run offline (no wallet/network). Some files
|
||||
(`test_group_*.py`, `test_no_willexecutor_karen7.py`, `parallel_ping_test.py`)
|
||||
exercise will-executor/network flows and need the live servers — don't rely on
|
||||
@@ -43,7 +51,8 @@ QT_QPA_PLATFORM=offscreen python3 tests/test_gui_common.py # GUI tests need of
|
||||
- **Ruff is NOT clean** (hundreds of pre-existing errors in `bal/` and
|
||||
`tests/`). Do not run `--fix` wholesale and do not try to silence everything;
|
||||
just avoid adding new violations. Config: `pyproject.toml` (line-length 88,
|
||||
E501 ignored).
|
||||
E501 ignored). Per-file ignores suppress `F403`/`F405` for the intentional
|
||||
`from .common import *` hub pattern in `bal/gui/qt/`.
|
||||
- Lint via the repo venv: `/home/steal/devel/bal/bal-electrum-plugin/venv/bin/ruff`
|
||||
- Typecheck: `pyright` (npm, `node_modules/`), config `pyrightconfig.json`
|
||||
(`extraPaths: ["../electrum"]`). Pyright reports many false positives on
|
||||
@@ -53,9 +62,18 @@ QT_QPA_PLATFORM=offscreen python3 tests/test_gui_common.py # GUI tests need of
|
||||
## Architecture
|
||||
|
||||
- `bal/core/` = GUI-free logic (`heirs.py`, `will.py`, `willexecutors.py`,
|
||||
`plugin_base.py`, `util.py`). Must never import Qt.
|
||||
`plugin_base.py`, `util.py`, `checkalive.py`, `reminders.py`,
|
||||
`input_rules.py`).
|
||||
Must never import Qt.
|
||||
- `bal/gui/qt/` = PyQt6 layer. `window.py` is the per-wallet controller,
|
||||
`plugin.py` is the Electrum `@hooks` entry, `qt.py` is a zipimport shim.
|
||||
`plugin.py` is the Electrum `@hooks` entry. `qt.py` is a zipimport shim.
|
||||
`common.py` uses `import *` intentionally (ruff suppresses F403/F405 here);
|
||||
`bal/gui/qt/*.py` all import from it.
|
||||
- `bal/cli/` = headless command-line layer (no Qt). `plugin.py` is the daemon
|
||||
entry point, `commands.py` registers `bal_*` commands with Electrum.
|
||||
- `bal/wallet_util/` = wallet helper utilities for Qt and core.
|
||||
- `bal/qt.py` and `bal/cmdline.py` are thin shims that Electrum discovers
|
||||
via `manifest.json`; they import the real `Plugin` class via `importlib`.
|
||||
- `bal/manifest.json` = version source of truth (Electrum reads it; also read by
|
||||
`make-release.sh`).
|
||||
- Compatibility constraint: must support Electrum **4.7.2 and 4.8.0**; the DB
|
||||
|
||||
340
CHANGELOG.md
340
CHANGELOG.md
@@ -2568,3 +2568,343 @@ of a session, without requiring the normal wizard flow to have run first.
|
||||
`test_merge_will_validity_error_logs_without_crashing`.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 49. OP_RETURN support for heirs
|
||||
|
||||
**Date:** 2026-07-30
|
||||
|
||||
**Goal:** 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 the normal amount
|
||||
calculations (percentage normalization, dust checks, leftover redistribution).
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/core/heirs.py`
|
||||
- `validate_heir`: new `OP_RETURN:<hex>` address validation (rejects non-hex,
|
||||
> 80 bytes). OP_RETURN heirs are stored with amount `"0"` and carry the raw
|
||||
hex data in the address field.
|
||||
- `prepare_lists`: OP_RETURN heirs are skipped during amount calculation
|
||||
(`normalize_perc`, dust checks). They are kept in the will but produce a
|
||||
zero-value output.
|
||||
- `buildTransactions`: when building the transaction, OP_RETURN heirs emit a
|
||||
`OP_RETURN <hex>` scriptPubKey output with value 0, matching Bitcoin's
|
||||
OP_RETURN output standard.
|
||||
- `get_transactions`: OP_RETURN heirs are grouped with their locktime peers
|
||||
but excluded from fee/dust arithmetic.
|
||||
|
||||
- `bal/gui/qt/window.py`
|
||||
- Heir dialog / wizard: the address field accepts `OP_RETURN:` prefix and
|
||||
shows a decoded-text message field when an OP_RETURN address is entered.
|
||||
- `build_will` / `_build_success_report`: OP_RETURN heirs display the
|
||||
decoded message text in the build report instead of a BTC address.
|
||||
|
||||
- `bal/gui/qt/lists.py`
|
||||
- Heir list: OP_RETURN heirs display the decoded message in the address
|
||||
column and show "0" for the amount.
|
||||
|
||||
- `bal/gui/qt/common.py`
|
||||
- Re-export the new `validate_op_return_hex` helper for the GUI layer.
|
||||
|
||||
- `tests/test_core_heirs.py`
|
||||
- 8 new tests: OP_RETURN validation (valid hex, too long, non-hex), OP_RETURN
|
||||
heirs excluded from amount calculations, OP_RETURN output shape in built
|
||||
transactions, mixed OP_RETURN + regular heirs.
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on changed production files: no new errors.
|
||||
- Full test suite: 307 passed.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 50. Core extraction: GUI-free logic into bal/core/ (reminders, checkalive, input_rules); RLock pickle fix
|
||||
|
||||
**Date:** 2026-08-05
|
||||
|
||||
**Goal:** extract GUI-free business logic that was previously embedded in Qt
|
||||
widgets (`bal/gui/qt/widgets.py`) into standalone `bal/core/` modules, making
|
||||
them independently testable without Qt. Also fix a critical `copy.deepcopy(tx)`
|
||||
crash on Electrum 4.8 (`RLock` cannot be pickled) and improve wallet-DB
|
||||
persistence robustness.
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/core/checkalive.py` (new)
|
||||
- `resolve_date_to_check`: computes the effective check-alive timestamp from
|
||||
will-settings and user type (BASIC uses `now()`; ADVANCED uses the stored
|
||||
threshold). Extracted from `window.py init_class_variables`.
|
||||
- `check_alive_expired`: pure-logic test for whether the check-alive has
|
||||
passed. Previously duplicated inline in `window.py`.
|
||||
|
||||
- `bal/core/reminders.py` (new)
|
||||
- `compute_reminder_offsets`, `basic_reminder_offsets`, `BALCalendar.write_ics`,
|
||||
`BALCalendar._ics_provider`: all calendar/reminder logic extracted from
|
||||
`widgets.py`. Generates iCal (.ics) files with separate VEVENT entries per
|
||||
reminder date. No Qt dependency.
|
||||
|
||||
- `bal/core/input_rules.py` (new)
|
||||
- `LockTimeRawEdit`, `LockTimeDateEdit`, `BalTimeEditWidget`,
|
||||
`ThresholdTimeWidget`: GUI-free data models for locktime/threshold
|
||||
validation and the Raw/Date selector logic. The Qt widgets in
|
||||
`widgets.py` now thin-wrap these helpers.
|
||||
|
||||
- `bal/core/heirs.py`
|
||||
- Fixed `copy.deepcopy(tx)` failure on Electrum 4.8: the `Transaction`
|
||||
object contains a `_thread.RLock` that cannot be pickled. Will-item
|
||||
persistence now re-parses the transaction from its hex serialization
|
||||
instead of deep-copying.
|
||||
- Invalid heirs (sentinel values from failed builds) are now kept in the
|
||||
wallet DB instead of being silently dropped, so the user can see and
|
||||
correct them.
|
||||
|
||||
- `bal/core/plugin_base.py`
|
||||
- Minor adjustments to support the extracted modules.
|
||||
|
||||
- `bal/gui/qt/widgets.py`
|
||||
- Major slim-down: business logic delegates to `bal/core/checkalive.py`,
|
||||
`bal/core/reminders.py`, and `bal/core/input_rules.py`. Only Qt widget
|
||||
creation and layout remain.
|
||||
|
||||
- `bal/gui/qt/calendar.py`
|
||||
- Adapted to use `bal/core/reminders.py` for .ics generation.
|
||||
|
||||
- `bal/gui/qt/window.py`
|
||||
- `init_class_variables` now calls `resolve_date_to_check` from
|
||||
`bal/core/checkalive.py` instead of computing inline.
|
||||
|
||||
- `bal/gui/qt/common.py`
|
||||
- Updated re-exports for the new core modules.
|
||||
|
||||
- Tests reorganized: core-only tests moved to `tests/test_core_checkalive.py`,
|
||||
`tests/test_core_reminders.py`, `tests/test_core_input_rules.py` (run
|
||||
without Qt).
|
||||
|
||||
- `tests/karen7`: fixture file compressed/updated for the new test structure.
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on changed files: no new errors.
|
||||
- Full test suite: 388 passed (significant increase due to new core test modules).
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 51. "Rebuild will on wallet close" setting (`REBUILD_ON_CLOSE`)
|
||||
|
||||
**Date:** 2026-08-14
|
||||
|
||||
**Goal:** add a new plugin setting that lets the plugin rebuild the will
|
||||
automatically when Electrum closes, skipping the full Build wizard. When
|
||||
enabled, closing Electrum triggers a one-shot prepare/inheritance flow
|
||||
(check, rebuild if needed, sign, broadcast) without showing the
|
||||
`BalBuildWillDialog`.
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/core/plugin_base.py`
|
||||
- New persisted config `REBUILD_ON_CLOSE = BalConfig(config,
|
||||
"bal_rebuild_on_close", False)` (default OFF), with explanatory comment.
|
||||
|
||||
- `bal/gui/qt/plugin.py`
|
||||
- New "Rebuild on close" checkbox in the settings dialog, bound to
|
||||
`REBUILD_ON_CLOSE`, with a tooltip explaining the behaviour. Added to the
|
||||
"Reset to Default Setting" list.
|
||||
|
||||
- `bal/gui/qt/window.py`
|
||||
- `on_close`: when `REBUILD_ON_CLOSE` is enabled, the close flow runs
|
||||
the auto-rebuild path (check + rebuild + sign + push) instead of the
|
||||
full wizard dialog. The legacy wizard-on-close path is kept when the
|
||||
setting is OFF.
|
||||
|
||||
- `tests/test_rebuild_on_close_setting.py` (new)
|
||||
- 8 tests: default OFF, toggle/persist, close triggers rebuild when ON,
|
||||
close skips rebuild when OFF, reset restores default.
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on changed files: no new errors.
|
||||
- Full test suite: 396 passed.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 52. Headless CLI layer (`bal/cli/`, `bal/cmdline.py`, `bal_*` daemon commands)
|
||||
|
||||
**Date:** 2026-08-14
|
||||
|
||||
**Goal:** expose the full BAL inheritance cycle via Electrum's command-line
|
||||
interface (daemon mode), without the Qt GUI. This enables scripting,
|
||||
automation, and headless server usage.
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/cmdline.py` (new)
|
||||
- Zip-import shim for Electrum's `gui_name='cmdline'` plugin loader.
|
||||
- Follows the same `importlib` pattern as `qt.py` but never imports Qt.
|
||||
- Re-exports `Plugin` from `bal.cli.plugin`.
|
||||
|
||||
- `bal/cli/__init__.py` (new)
|
||||
- Registers the `bal_*` commands with Electrum on import.
|
||||
|
||||
- `bal/cli/plugin.py` (new)
|
||||
- `Plugin(BalPlugin)` -- minimal entry point for the daemon (no Qt hooks,
|
||||
no `bal_windows`).
|
||||
|
||||
- `bal/cli/commands.py` (new)
|
||||
- 30 `@plugin_command` async functions registered as `bal_*` commands:
|
||||
settings (list/get/set/reset), heirs (list/show/add/update/delete/import/
|
||||
export), will-executors (list/show/add/update/select/delete/ping/download/
|
||||
import/export), will (status/check/prepare/sign/broadcast/export/
|
||||
import_merge/invalidate/check_executor).
|
||||
- Each command is a thin transport layer: validates args, delegates to
|
||||
`BalController`, returns JSON-serializable results.
|
||||
|
||||
- `bal/cli/controller.py` (new, ~1100 lines)
|
||||
- `BalController` -- headless replica of `BalWindow`. Reads/writes wallet DB,
|
||||
config, and will-executors without any Qt dependency.
|
||||
- Methods mirror `BalWindow` flows: `load_willitems`, `save_willitems`,
|
||||
`init_class_variables`, `build_inheritance_transaction`, `sign_transactions`,
|
||||
`push_transactions_to_willexecutors`, `check_transactions`, `export_json_file`,
|
||||
`merge_will_from_file`, `invalidate_will`.
|
||||
- Domain exceptions (`WillExpiredException`, `HeirNotFoundException`, etc.)
|
||||
are converted to `UserFacingException` with clear text.
|
||||
|
||||
- `bal/manifest.json`
|
||||
- `"available_for"` updated from `["qt"]` to `["qt", "cmdline"]`.
|
||||
|
||||
- `bal/__init__.py`
|
||||
- Added `from .cli import commands` to register `bal_*` commands on import
|
||||
(both CLI pre-parse and GUI startup).
|
||||
|
||||
- `tests/test_cli_commands_registered.py` (new)
|
||||
- 4 tests: all `bal_*` commands registered, all are coroutines, no duplicate
|
||||
registration, all args documented.
|
||||
|
||||
- `tests/test_cli_controller_offline.py` (new)
|
||||
- Offline CRUD tests for heirs, will-executors, settings, and will
|
||||
import/export merge via `BalController` (no network).
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on new files: clean.
|
||||
- Full test suite: 427 passed.
|
||||
- `tests/test_cli_commands_registered.py`: all 4 tests pass.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 53. Auto-rebuild on new transactions (`AUTO_REBUILD`)
|
||||
|
||||
**Date:** 2026-08-16
|
||||
|
||||
**Goal:** when new transactions are detected in the wallet (e.g. incoming
|
||||
payments), automatically rebuild the will so the inheritance covers the
|
||||
new UTXOs. The delivery date is anticipated by one day to orphan the old
|
||||
will on-chain; an on-chain invalidation is only needed when the anticipated
|
||||
locktime crosses the Check Alive threshold.
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/core/plugin_base.py`
|
||||
- New persisted config `AUTO_REBUILD = BalConfig(config,
|
||||
"bal_auto_rebuild", False)` (default OFF), with explanatory comment.
|
||||
|
||||
- `bal/gui/qt/plugin.py`
|
||||
- New "Auto-rebuild" checkbox in the settings dialog, bound to
|
||||
`AUTO_REBUILD`, with a tooltip. Added to the "Reset to Default Setting"
|
||||
list.
|
||||
|
||||
- `bal/gui/qt/window.py`
|
||||
- New `_auto_rebuild_on_new_tx()` method: triggered when Electrum detects
|
||||
a new transaction in the wallet. Runs the full prepare flow: check
|
||||
coherence, rebuild with the anticipated locktime (delivery date minus 1
|
||||
day), persist, sign (if passwordless), push to will-executors.
|
||||
- When the anticipated locktime crosses the Check Alive threshold, returns
|
||||
an invalidation transaction instead of auto-completing.
|
||||
- Connected to Electrum's `new_transaction` signal.
|
||||
|
||||
- `tests/test_auto_rebuild_on_new_tx.py` (new)
|
||||
- 16 tests: AUTO_REBUILD default OFF, toggle/persist, rebuild triggers on
|
||||
new tx, locktime anticipation by 1 day, threshold crossing returns
|
||||
invalidation, passwordless wallet signs automatically, encrypted wallet
|
||||
returns invalidation tx for manual signing.
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on changed files: no new errors.
|
||||
- Full test suite: 432 passed.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 54. CLI `bal_will_autorebuild` command
|
||||
|
||||
**Date:** 2026-08-16
|
||||
|
||||
**Goal:** expose the auto-rebuild flow (entry #53) as a headless CLI command,
|
||||
so scripts and daemons can trigger the one-shot check/rebuild/sign/push
|
||||
cycle without the Qt GUI.
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/cli/commands.py`
|
||||
- New `bal_will_autorebuild` async command (flag `nw`): runs the full
|
||||
auto-rebuild flow via `BalController.auto_rebuild()`. Returns a JSON
|
||||
object with `result` (`valid`, `no_heirs`, `invalidated`, `nothing`,
|
||||
`needs_signing`, `rebuilt`) and, when applicable, the invalidation
|
||||
transaction.
|
||||
|
||||
- `bal/cli/controller.py`
|
||||
- New `auto_rebuild()` method: headless replica of the GUI auto-rebuild
|
||||
flow. Checks coherence, rebuilds with anticipated locktime, handles
|
||||
threshold-crossing (returns invalidation tx), signs passwordless wallets
|
||||
automatically, pushes to will-executors.
|
||||
|
||||
- `tests/test_cli_autorebuild.py` (new)
|
||||
- 10 tests: auto-rebuild returns `valid` when will is coherent, `rebuilt`
|
||||
when rebuilt, `invalidated` with invalidation tx when threshold crossed,
|
||||
`needs_signing` for encrypted wallets, `no_heirs` when heirs are missing.
|
||||
|
||||
- `tests/test_cli_commands_registered.py`
|
||||
- Updated `EXPECTED_COMMANDS` to include `bal_will_autorebuild`.
|
||||
|
||||
- `tests/test_cli_controller_offline.py`
|
||||
- Extended with auto-rebuild flow tests.
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on changed files: no new errors.
|
||||
- Full test suite: 438 passed.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
---
|
||||
|
||||
## 55. Remove "Add transaction without willexecutor" from settings dialog
|
||||
|
||||
**Date:** 2026-08-17
|
||||
|
||||
**Goal (owner request):** the "Add transaction without willexecutor" checkbox
|
||||
was already available in the Will-Executor tab; showing it redundantly in the
|
||||
settings dialog created confusion. Remove it from the settings dialog and
|
||||
renumber the grid rows.
|
||||
|
||||
**What changed:**
|
||||
|
||||
- `bal/gui/qt/plugin.py`
|
||||
- Removed the "Add transaction without willexecutor" checkbox
|
||||
(`NO_WILLEXECUTOR`) from the settings dialog grid. The setting is still
|
||||
functional (available from the Will-Executor tab and the wizard); only
|
||||
the settings-dialog exposure was removed.
|
||||
- Grid rows 5--16 renumbered to 4--15 to close the gap left by the removal.
|
||||
- Removed the corresponding reset-button widget for `NO_WILLEXECUTOR` from
|
||||
the "Reset to Default Setting" list.
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on changed file: no new errors.
|
||||
- Full test suite: 438 passed (unchanged).
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
19
HANDOFF.md
19
HANDOFF.md
@@ -58,6 +58,7 @@ bal/ <- the plugin package (this is what ships in the ZI
|
||||
__init__.py <- package docstring (no version here anymore)
|
||||
manifest.json <- plugin manifest, "version" field (SINGLE SOURCE OF TRUTH for the version)
|
||||
qt.py <- zipimport shim used when loaded as an external ZIP plugin
|
||||
cmdline.py <- CLI entry-point shim (Electrum gui_name='cmdline')
|
||||
core/
|
||||
plugin_base.py <- get_version() reads the version from manifest.json (zip-safe)
|
||||
heirs.py <- HEIRS + transaction building (prepare_lists,
|
||||
@@ -67,6 +68,14 @@ bal/ <- the plugin package (this is what ships in the ZI
|
||||
willexecutors.py <- remote will-executor services handling (is_selected / is_valid,
|
||||
parallel push/check).
|
||||
util.py <- locktime parsing/most helpers (timestamps only).
|
||||
checkalive.py <- resolve_date_to_check, check_alive_expired (GUI-free).
|
||||
reminders.py <- compute_reminder_offsets, BALCalendar .ics generation (GUI-free).
|
||||
input_rules.py <- locktime/threshold data models, Raw/Date selector logic (GUI-free).
|
||||
cli/ <- headless command-line layer (no Qt)
|
||||
__init__.py <- registers bal_* commands on import
|
||||
commands.py <- bal_* daemon commands (@plugin_command, async, thin transport)
|
||||
controller.py <- BalController: headless replica of BalWindow (no Qt)
|
||||
plugin.py <- CLI Plugin entry point (extends BalPlugin, no Qt hooks)
|
||||
gui/qt/
|
||||
common.py <- shared imports; every gui module does
|
||||
`from .common import *`. Add new shared imports HERE.
|
||||
@@ -80,7 +89,6 @@ bal/ <- the plugin package (this is what ships in the ZI
|
||||
wallet_util/ <- standalone wallet-inspection helpers, no Qt
|
||||
tests/ <- standalone test scripts (see Section 3).
|
||||
docs/ <- user manual + inheritance-options guide (.md sources).
|
||||
bal_cli.py <- headless CLI (heirs/will build/sign/push/check), no Qt.
|
||||
build_zip.py <- builds the shippable ZIP (36 files).
|
||||
CHANGELOG.md <- numbered task log (English).
|
||||
.agent_memory_tasks.md <- terse internal memory notes per task batch.
|
||||
@@ -295,8 +303,8 @@ See Section 5 for details.
|
||||
update `GITEA_TOKEN` env var or `~/.git-credentials`, then retry.
|
||||
- Older PR history (pre-`main` direct workflow): **#13** (v0.4.7), **#14**
|
||||
(docs/DUST section + translation), **#15** (v0.4.8), **#4** (v0.6.1 —
|
||||
manifest.json version). All merged into `main`.
|
||||
- Releases: latest is **v0.6.1**; v0.6.0 and v0.5.18 before it; the older
|
||||
manifest.json version); all merged into `main`.
|
||||
- Releases: latest is **v0.7.0**; v0.6.1, v0.6.0 and v0.5.18 before it; the older
|
||||
v0.2.x line is kept in history.
|
||||
|
||||
---
|
||||
@@ -346,6 +354,11 @@ See Section 5 for details.
|
||||
- **#47 / #48 (post-v0.6.1)** — `is_selected`/`is_valid` fee bounds (extremes
|
||||
allowed) and the `merge_will` missing-`date_to_check` crash fix (see
|
||||
CHANGELOG).
|
||||
- **v0.7.0** — OP_RETURN heirs; core extraction (checkalive, reminders,
|
||||
input_rules); RLock pickle fix; `REBUILD_ON_CLOSE`; headless CLI layer
|
||||
(`bal/cli/`, `bal/cmdline.py`, 30 `bal_*` commands); `AUTO_REBUILD` on new
|
||||
transactions; `bal_will_autorebuild` CLI command; removed redundant
|
||||
"Add transaction without willexecutor" from settings dialog.
|
||||
|
||||
### Open / suspended / backlog items (see `.agent_memory_tasks.md` for detail)
|
||||
- **SUSPENDED — "(UTC)" label in the wizard.** The owner asked to show an
|
||||
|
||||
56
README.md
56
README.md
@@ -5,10 +5,11 @@ Free and decentralized **Bitcoin inheritance** support for the
|
||||
that transfer your funds to your heirs if you stop refreshing them
|
||||
(dead-man's switch), optionally relayed by will-executor servers.
|
||||
|
||||
This repository contains a **behavior-preserving refactor** of the original
|
||||
plugin. The logic was kept byte-identical wherever possible; only the file
|
||||
layout was reorganized to cleanly separate **business logic** from the
|
||||
**PyQt GUI**.
|
||||
This repository contains a **refactored and extended** version of the original
|
||||
plugin. The logic was reorganized to cleanly separate **business logic** from the
|
||||
**PyQt GUI**, and new features have been added including a headless CLI,
|
||||
auto-rebuild on new transactions, OP_RETURN heirs, and configurable calendar
|
||||
reminders.
|
||||
|
||||
## Repository layout
|
||||
|
||||
@@ -16,12 +17,20 @@ layout was reorganized to cleanly separate **business logic** from the
|
||||
bal/ the installable Electrum plugin package
|
||||
├── manifest.json plugin metadata (Electrum reads this)
|
||||
├── qt.py Qt entry-point shim (re-exports Plugin)
|
||||
├── cmdline.py CLI entry-point shim (re-exports Plugin)
|
||||
├── core/ GUI-free logic (importable without Qt)
|
||||
│ ├── util.py
|
||||
│ ├── plugin_base.py
|
||||
│ ├── heirs.py
|
||||
│ ├── will.py
|
||||
│ └── willexecutors.py
|
||||
│ ├── willexecutors.py
|
||||
│ ├── checkalive.py
|
||||
│ ├── reminders.py
|
||||
│ └── input_rules.py
|
||||
├── cli/ headless command-line layer (no Qt)
|
||||
│ ├── commands.py bal_* daemon commands (@plugin_command)
|
||||
│ ├── controller.py headless BalController (replicates BalWindow)
|
||||
│ └── plugin.py CLI Plugin entry point
|
||||
├── gui/qt/ PyQt6 presentation layer
|
||||
│ ├── theme.py status → color mapping
|
||||
│ ├── common.py shared imports / helpers
|
||||
@@ -30,6 +39,7 @@ bal/ the installable Electrum plugin package
|
||||
│ ├── dialogs.py dialog windows
|
||||
│ ├── lists.py tree/list views
|
||||
│ ├── window.py per-wallet GUI controller
|
||||
│ ├── window_utils.py GUI utility helpers
|
||||
│ └── plugin.py Plugin (Electrum @hooks → GUI)
|
||||
├── icons/ wallet_util/ LICENSE README.md
|
||||
build_zip.py builds a clean, zipimport-friendly distribution zip
|
||||
@@ -77,6 +87,42 @@ Copy the `bal/` directory into your Electrum installation's
|
||||
`electrum/plugins/` directory, so that `electrum/plugins/bal/manifest.json`
|
||||
exists, then enable it from **Tools → Plugins**.
|
||||
|
||||
## Command-line / headless usage
|
||||
|
||||
BAL can be used without the Qt GUI via Electrum's daemon mode. The CLI layer
|
||||
exposes `bal_*` commands that replicate the full inheritance cycle.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- An **Electrum daemon** running (`electrum daemon -d`)
|
||||
- A wallet loaded (`electrum load_wallet`)
|
||||
|
||||
### Available commands
|
||||
|
||||
| Category | Commands |
|
||||
|----------|----------|
|
||||
| Settings | `bal_settings_list`, `bal_settings_get`, `bal_settings_set`, `bal_settings_reset` |
|
||||
| Heirs | `bal_heirs_list`, `bal_heirs_show`, `bal_heirs_add`, `bal_heirs_update`, `bal_heirs_delete`, `bal_heirs_import`, `bal_heirs_export` |
|
||||
| Will-Executors | `bal_willexecutors_list`, `bal_willexecutors_show`, `bal_willexecutors_add`, `bal_willexecutors_update`, `bal_willexecutors_select`, `bal_willexecutors_delete`, `bal_willexecutors_ping`, `bal_willexecutors_download`, `bal_willexecutors_import`, `bal_willexecutors_export` |
|
||||
| Will | `bal_will_status`, `bal_will_check`, `bal_will_prepare`, `bal_will_autorebuild`, `bal_will_sign`, `bal_will_broadcast`, `bal_will_export`, `bal_will_import_merge`, `bal_will_invalidate`, `bal_will_check_executor` |
|
||||
|
||||
### Example workflow
|
||||
|
||||
```bash
|
||||
electrum daemon -d
|
||||
electrum load_wallet
|
||||
electrum bal_heirs_list
|
||||
electrum bal_will_prepare
|
||||
electrum bal_will_sign --password '...'
|
||||
electrum bal_will_broadcast
|
||||
electrum stop
|
||||
```
|
||||
|
||||
All commands require a running daemon (Electrum's `plugin_command` enforces
|
||||
this). Wallet-bound commands (`bal_heirs_*`, `bal_will_*`, etc.) require the
|
||||
wallet to be loaded first. Signing commands require `--password` for encrypted
|
||||
wallets.
|
||||
|
||||
## Inheritance safety: anticipate / postpone
|
||||
|
||||
A will transaction is signed with a **fixed, immutable locktime** and then
|
||||
|
||||
@@ -10,7 +10,7 @@ Pure, GUI-free. The GUI raises :class:`CheckAliveError` to trigger the
|
||||
postpone/invalidate flow; the decision that it *should* be raised lives here.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from .plugin_base import BalTimestamp
|
||||
@@ -24,7 +24,7 @@ class CheckAliveError(Exception):
|
||||
|
||||
def __str__(self):
|
||||
return "Check alive expired please update it: {}".format(
|
||||
datetime.fromtimestamp(self.timestamp_to_check).isoformat()
|
||||
datetime.fromtimestamp(self.timestamp_to_check, tz=timezone.utc).isoformat()
|
||||
)
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ def resolve_date_to_check(
|
||||
The reference timestamp (float, UNIX seconds).
|
||||
"""
|
||||
if is_basic_mode:
|
||||
return (now if now is not None else datetime.now().timestamp())
|
||||
return (now if now is not None else datetime.now(tz=timezone.utc).timestamp())
|
||||
|
||||
threshold = BalTimestamp(will_settings["threshold"])
|
||||
# A RELATIVE threshold ("30d"/"1y") means "N days BEFORE the delivery":
|
||||
@@ -107,5 +107,5 @@ def check_alive_expired(
|
||||
"""
|
||||
if is_basic_mode:
|
||||
return False
|
||||
current = now if now is not None else datetime.now().timestamp()
|
||||
current = now if now is not None else datetime.now(tz=timezone.utc).timestamp()
|
||||
return date_to_check < current
|
||||
|
||||
@@ -24,7 +24,7 @@ This module performs **no** GUI work and imports nothing from PyQt / electrum.gu
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
from datetime import date, datetime, timedelta
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
|
||||
from electrum import constants, json_db
|
||||
from electrum.logging import get_logger
|
||||
@@ -460,8 +460,8 @@ class BalPlugin(BasePlugin):
|
||||
def default_will_settings_absolute():
|
||||
"""Convert the default relative dates into absolute timestamps (from today)."""
|
||||
relative_dates = BalPlugin.default_will_settings_relative()
|
||||
today = date.today()
|
||||
dt = datetime(today.year, today.month, today.day, 0, 0, 0)
|
||||
today = datetime.now(tz=timezone.utc).date()
|
||||
dt = datetime(today.year, today.month, today.day, 0, 0, 0, tzinfo=timezone.utc)
|
||||
threshold = (
|
||||
dt + timedelta(days=BalTimestamp(relative_dates["threshold"]).duration_to_days())
|
||||
).timestamp()
|
||||
@@ -521,12 +521,12 @@ class BalTimestamp:
|
||||
"""
|
||||
int32_max = 2 ** 31 - 1
|
||||
try:
|
||||
return datetime.fromtimestamp(ts)
|
||||
return datetime.fromtimestamp(ts, tz=timezone.utc)
|
||||
except (OSError, OverflowError, ValueError):
|
||||
try:
|
||||
return datetime.fromtimestamp(min(int(ts), int32_max))
|
||||
return datetime.fromtimestamp(min(int(ts), int32_max), tz=timezone.utc)
|
||||
except (OSError, OverflowError, ValueError):
|
||||
return datetime.fromtimestamp(int32_max)
|
||||
return datetime.fromtimestamp(int32_max, tz=timezone.utc)
|
||||
|
||||
def to_date(self, from_date=None, reverse=False):
|
||||
"""Resolve to a ``datetime``.
|
||||
@@ -539,7 +539,7 @@ class BalTimestamp:
|
||||
return self._safe_fromtimestamp(self.value)
|
||||
else:
|
||||
if from_date is None:
|
||||
from_date = datetime.now()
|
||||
from_date = datetime.now(tz=timezone.utc)
|
||||
if isinstance(from_date, (int, float)):
|
||||
from_date = self._safe_fromtimestamp(from_date)
|
||||
reverse = 1 if not reverse else -1
|
||||
|
||||
@@ -38,7 +38,7 @@ def compute_reminder_offsets(days, count):
|
||||
count: requested number of reminders.
|
||||
|
||||
Returns:
|
||||
A list of integer day-offsets (each ``>= 1``), e.g. ``[22, 15, 8]`` for
|
||||
A list of integer day-offsets (each ``>= 1``), e.g. ``[30, 16, 1]`` for
|
||||
``days=30, count=3``. Empty if there is no room for any reminder.
|
||||
"""
|
||||
# No room for any reminder (deadline today or already passed).
|
||||
|
||||
@@ -18,7 +18,7 @@ original implementation.
|
||||
"""
|
||||
|
||||
import bisect
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from electrum.address_synchronizer import TX_HEIGHT_FUTURE, TX_HEIGHT_LOCAL
|
||||
from electrum.transaction import PartialTxOutput
|
||||
@@ -103,7 +103,7 @@ class Util:
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
now = datetime.now()
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
if locktime[-1] == "y":
|
||||
locktime = str(int(locktime[:-1]) * 365) + "d"
|
||||
if locktime[-1] == "d":
|
||||
@@ -189,7 +189,7 @@ class Util:
|
||||
# moment, so fall back to the legacy forward-from-now resolution.
|
||||
return Util.parse_locktime_string(current)
|
||||
try:
|
||||
base = datetime.fromtimestamp(int(tx_locktime)).replace(
|
||||
base = datetime.fromtimestamp(int(tx_locktime), tz=timezone.utc).replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
build_moment = base - timedelta(days=built_days)
|
||||
@@ -440,9 +440,9 @@ class Util:
|
||||
# On Windows datetime.fromtimestamp raises OverflowError past 2038
|
||||
# (e.g. NLOCKTIME_MAX); clamp to INT32_MAX (Electrum issue #6170).
|
||||
try:
|
||||
dt = datetime.fromtimestamp(locktime)
|
||||
dt = datetime.fromtimestamp(locktime, tz=timezone.utc)
|
||||
except (OverflowError, OSError, ValueError):
|
||||
dt = datetime.fromtimestamp(min(locktime, 2 ** 31 - 1))
|
||||
dt = datetime.fromtimestamp(min(locktime, 2 ** 31 - 1), tz=timezone.utc)
|
||||
dt -= timedelta(seconds=seconds)
|
||||
out = dt.timestamp()
|
||||
|
||||
@@ -450,34 +450,6 @@ class Util:
|
||||
out = 1
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def cmp_locktime(locktimea, locktimeb):
|
||||
"""Compare two relative locktime strings sharing the same unit."""
|
||||
if locktimea == locktimeb:
|
||||
return 0
|
||||
strlocktimea = str(locktimea)
|
||||
strlocktimeb = str(locktimeb)
|
||||
if locktimea[-1] in "ydb":
|
||||
if locktimeb[-1] == locktimea[-1]:
|
||||
return int(strlocktimea[-1]) - int(strlocktimeb[-1])
|
||||
else:
|
||||
return int(locktimea) - (locktimeb)
|
||||
|
||||
@staticmethod
|
||||
def get_lowest_valid_tx(available_utxos, will):
|
||||
"""Placeholder kept from the original code (sorts the will by locktime)."""
|
||||
will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
|
||||
for _txid, _willitem in will.items():
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_locktimes(will):
|
||||
"""Return the distinct locktimes used by the transactions in ``will``."""
|
||||
locktimes = {}
|
||||
for _, willitem in will.items():
|
||||
locktimes[willitem["tx"].locktime] = True
|
||||
return locktimes.keys()
|
||||
|
||||
@staticmethod
|
||||
def get_lowest_locktimes(locktimes):
|
||||
"""Split a list of locktimes into (sorted_timestamps, sorted_blocks)."""
|
||||
@@ -492,32 +464,6 @@ class Util:
|
||||
|
||||
return sorted(sorted_timestamp), sorted(sorted_block)
|
||||
|
||||
@staticmethod
|
||||
def get_lowest_locktimes_from_will(will):
|
||||
"""Convenience wrapper: lowest locktimes directly from a will dict."""
|
||||
return Util.get_lowest_locktimes(Util.get_locktimes(will))
|
||||
|
||||
@staticmethod
|
||||
def search_willtx_per_io(will, tx):
|
||||
"""Find a will entry whose tx has the same inputs/outputs as ``tx``."""
|
||||
for wid, w in will.items():
|
||||
if Util.cmp_txs(w["tx"], tx["tx"]):
|
||||
return wid, w
|
||||
return None, None
|
||||
|
||||
@staticmethod
|
||||
def invalidate_will(will):
|
||||
raise Exception("not implemented")
|
||||
|
||||
@staticmethod
|
||||
def get_will_spent_utxos(will):
|
||||
"""Collect every input spent by any transaction in ``will``."""
|
||||
utxos = []
|
||||
for _, willitem in will.items():
|
||||
utxos += willitem["tx"].inputs()
|
||||
|
||||
return utxos
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# UTXO helpers
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@@ -74,11 +74,6 @@ class Will:
|
||||
if not will[child[0]].father:
|
||||
will[child[0]].father = willid
|
||||
|
||||
# return a list of will sorted by locktime
|
||||
@staticmethod
|
||||
def get_sorted_will(will):
|
||||
return sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
|
||||
|
||||
@staticmethod
|
||||
def only_valid(will):
|
||||
for k, v in will.items():
|
||||
@@ -107,15 +102,6 @@ class Will:
|
||||
and not w.get_status("CHECKED")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def search_equal_tx(will, tx, wid):
|
||||
for w in will:
|
||||
if w != wid and not tx.to_json() != will[w]["tx"].to_json():
|
||||
if will[w]["tx"].txid() != tx.txid():
|
||||
if Util.cmp_txs(will[w]["tx"], tx):
|
||||
return will[w]["tx"]
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_tx_from_any(x):
|
||||
try:
|
||||
@@ -516,6 +502,7 @@ class Will:
|
||||
for _wid, w in will.items():
|
||||
if w.get_status("VALID") and not w.get_status("COMPLETE"):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def search_rai(all_inputs, all_utxos, will, wallet):
|
||||
@@ -1345,6 +1332,8 @@ class WillItem(Logger):
|
||||
WillItem,
|
||||
):
|
||||
self.__dict__ = w.__dict__.copy()
|
||||
self.STATUS = copy.deepcopy(w.STATUS)
|
||||
self.heirs = copy.deepcopy(w.heirs) if w.heirs is not None else None
|
||||
else:
|
||||
self.tx = Will.get_tx_from_any(w["tx"])
|
||||
self.heirs = w.get("heirs", None)
|
||||
|
||||
@@ -13,12 +13,16 @@ The pure RFC-5545 logic (offsets, escaping, folding, the unified .ics builder,
|
||||
the Qt button and the OS/subprocess glue.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from PyQt6.QtGui import QAction
|
||||
from PyQt6.QtWidgets import QToolButton
|
||||
from PyQt6.QtWidgets import QInputDialog, QMenu, QToolButton
|
||||
|
||||
from electrum.gui.qt.util import getSaveFileName
|
||||
|
||||
from ...core.reminders import write_temp_ics
|
||||
from .common import *
|
||||
from .common import _, _logger # underscore names are not re-exported by "import *"
|
||||
from .common import _, _logger
|
||||
|
||||
|
||||
class BalCalendarButton(QToolButton):
|
||||
|
||||
@@ -22,8 +22,61 @@ from typing import TYPE_CHECKING
|
||||
from ...core.checkalive import CheckAliveError
|
||||
from ...core.reminders import build_ics_reminders
|
||||
from .calendar import BalCalendarButton
|
||||
from .common import *
|
||||
from .common import _, _logger # underscore names are not re-exported by "import *"
|
||||
from .common import (
|
||||
_,
|
||||
_logger,
|
||||
AmountException,
|
||||
Any,
|
||||
BalTimestamp,
|
||||
BestEffortRequestFailed,
|
||||
Buttons,
|
||||
Callable,
|
||||
CancelButton,
|
||||
HEIR_DUST_AMOUNT,
|
||||
HEIR_REAL_AMOUNT,
|
||||
HeirAmountIsDustException,
|
||||
HeirChangeException,
|
||||
HeirNotFoundException,
|
||||
MessageBoxMixin,
|
||||
Network,
|
||||
NoHeirsException,
|
||||
NoWillExecutorNotPresent,
|
||||
NotCompleteWillException,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QTimer,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
Qt,
|
||||
TaskThread,
|
||||
TxBroadcastError,
|
||||
TxFeesChangedException,
|
||||
Util,
|
||||
Will,
|
||||
WillExecutorFeeTooHighException,
|
||||
WillExecutorNotPresent,
|
||||
WillExpiredException,
|
||||
WillPostponedException,
|
||||
WillexecutorChangeException,
|
||||
Willexecutors,
|
||||
bring_to_front,
|
||||
decimal_point_to_base_unit_name,
|
||||
import_meta_gui,
|
||||
partial,
|
||||
pyqtSignal,
|
||||
read_QIcon_from_bytes,
|
||||
read_json_file,
|
||||
show_modal,
|
||||
show_on_top,
|
||||
stop_thread,
|
||||
time,
|
||||
top_level_of,
|
||||
)
|
||||
from .widgets import (
|
||||
WillSettingsWidget,
|
||||
WillWidget,
|
||||
|
||||
@@ -19,8 +19,59 @@ from typing import TYPE_CHECKING
|
||||
from PyQt6.QtWidgets import QLineEdit as _QLineEdit
|
||||
from PyQt6.QtWidgets import QMessageBox, QStyledItemDelegate
|
||||
|
||||
from .common import *
|
||||
from .common import _, _logger # underscore names are not re-exported by "import *"
|
||||
from .common import (
|
||||
_,
|
||||
_logger,
|
||||
BalTimestamp,
|
||||
Buttons,
|
||||
CancelButton,
|
||||
HelpButton,
|
||||
MessageBoxMixin,
|
||||
MyTreeView,
|
||||
OP_RETURN_PREFIX,
|
||||
OkButton,
|
||||
QAbstractItemView,
|
||||
QApplication,
|
||||
QColor,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMenu,
|
||||
QModelIndex,
|
||||
QPersistentModelIndex,
|
||||
QPushButton,
|
||||
QSize,
|
||||
QSizePolicy,
|
||||
QSpinBox,
|
||||
QStandardItem,
|
||||
QStandardItemModel,
|
||||
QToolButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
Qt,
|
||||
TaskThread,
|
||||
Util,
|
||||
Will,
|
||||
WillItem,
|
||||
Willexecutors,
|
||||
char_width_in_lineedit,
|
||||
datetime,
|
||||
enum,
|
||||
export_meta_gui,
|
||||
getOpenFileName,
|
||||
import_meta_gui,
|
||||
is_op_return_address,
|
||||
partial,
|
||||
read_QIcon_from_bytes,
|
||||
read_json_file,
|
||||
server_status_text,
|
||||
server_status_tooltip,
|
||||
signature_suffix,
|
||||
status_color,
|
||||
tx_from_any,
|
||||
write_json_file,
|
||||
)
|
||||
from .dialogs import BalBuildWillDialog, BalDialog
|
||||
from .widgets import BalCheckBox, WillSettingsWidget
|
||||
|
||||
|
||||
@@ -15,14 +15,35 @@ and cached in ``self.bal_windows``.
|
||||
"""
|
||||
|
||||
from electrum.gui.qt.main_window import StatusBarButton
|
||||
from electrum.plugin import hook
|
||||
from electrum.util import EventListener, event_listener
|
||||
from PyQt6.QtWidgets import QLayout
|
||||
|
||||
from .common import *
|
||||
from .common import ( # underscore names are not re-exported by "import *"
|
||||
from .common import (
|
||||
_,
|
||||
_logger,
|
||||
BalPlugin,
|
||||
Buttons,
|
||||
EnterButton,
|
||||
HelpButton,
|
||||
PasswordDialog,
|
||||
QComboBox,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QInputDialog,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QTimer,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
UserCancelled,
|
||||
Willexecutors,
|
||||
add_widget,
|
||||
partial,
|
||||
read_QIcon_from_bytes,
|
||||
read_QPixmap_from_bytes,
|
||||
show_modal,
|
||||
webopen,
|
||||
)
|
||||
from .dialogs import BalDialog
|
||||
from .widgets import BalCheckBox, BalLineEdit, BalSpinBox, BalTextEdit
|
||||
|
||||
@@ -28,8 +28,53 @@ from ...core.input_rules import (
|
||||
)
|
||||
from ...core.reminders import build_ics_reminders, write_temp_ics
|
||||
from .calendar import BalCalendar, BalCalendarButton
|
||||
from .common import *
|
||||
from .common import _, _logger # underscore names are not re-exported by "import *"
|
||||
from .common import (
|
||||
_,
|
||||
_logger,
|
||||
Any,
|
||||
BTCAmountEdit,
|
||||
BalTimestamp,
|
||||
ColorScheme,
|
||||
DECIMAL_POINT,
|
||||
Decimal,
|
||||
HelpButton,
|
||||
NLOCKTIME_BLOCKHEIGHT_MAX,
|
||||
NLOCKTIME_MAX,
|
||||
Optional,
|
||||
QAbstractSpinBox,
|
||||
QCheckBox,
|
||||
QColor,
|
||||
QComboBox,
|
||||
QDateTime,
|
||||
QDateTimeEdit,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPainter,
|
||||
QPalette,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QSpinBox,
|
||||
QStyle,
|
||||
QStyleOptionFrame,
|
||||
QTextEdit,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
Qt,
|
||||
Union,
|
||||
Util,
|
||||
Will,
|
||||
char_width_in_lineedit,
|
||||
datetime,
|
||||
getSaveFileName,
|
||||
log_error,
|
||||
os,
|
||||
partial,
|
||||
pyqtSignal,
|
||||
read_QIcon_from_bytes,
|
||||
signature_suffix,
|
||||
status_color,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .window import BalWindow
|
||||
|
||||
@@ -24,8 +24,63 @@ from ...core.checkalive import (
|
||||
check_alive_expired,
|
||||
resolve_date_to_check,
|
||||
)
|
||||
from .common import *
|
||||
from .common import _, _logger # underscore names are not re-exported by "import *"
|
||||
from .common import (
|
||||
_,
|
||||
_logger,
|
||||
AmountException,
|
||||
BalPlugin,
|
||||
Buttons,
|
||||
CancelButton,
|
||||
ElectrumWindow,
|
||||
FileImportFailed,
|
||||
HeirChangeException,
|
||||
HeirNotFoundException,
|
||||
Heirs,
|
||||
HelpButton,
|
||||
Mapping,
|
||||
Network,
|
||||
NoHeirsException,
|
||||
NoWillExecutorNotPresent,
|
||||
NotCompleteWillException,
|
||||
OP_RETURN_PREFIX,
|
||||
OkButton,
|
||||
PaymentIdentifier,
|
||||
QGridLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QTimer,
|
||||
QVBoxLayout,
|
||||
SerializationError,
|
||||
Transaction,
|
||||
TxDialog,
|
||||
TxFeesChangedException,
|
||||
Util,
|
||||
Will,
|
||||
WillExecutorFeeTooHighException,
|
||||
WillExecutorNotPresent,
|
||||
WillExpiredException,
|
||||
WillItem,
|
||||
WillPostponedException,
|
||||
WillexecutorChangeException,
|
||||
Willexecutors,
|
||||
char_width_in_lineedit,
|
||||
copy,
|
||||
export_meta_gui,
|
||||
import_meta_gui,
|
||||
is_onion_url,
|
||||
is_op_return_address,
|
||||
is_tor_active,
|
||||
log_error,
|
||||
partial,
|
||||
read_QIcon_from_bytes,
|
||||
read_json_file,
|
||||
show_on_top,
|
||||
shown_cv,
|
||||
time,
|
||||
tx_from_any,
|
||||
write_json_file,
|
||||
)
|
||||
from .dialogs import (
|
||||
BalBuildWillDialog,
|
||||
BalDialog,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bal",
|
||||
"fullname": "Bitcoin After Life",
|
||||
"version": "0.6.1",
|
||||
"version": "0.7.0",
|
||||
"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",
|
||||
|
||||
@@ -308,7 +308,7 @@ executor that <em>should</em> hold your tx did not return it — re‑Broadcast
|
||||
<li><strong>Mind the dust limit.</strong> A share below Bitcoin's dust limit is skipped; if <strong>every</strong> heir is dust the build is blocked with a clear message (§4.8) — raise the amounts or use fewer heirs.</li>
|
||||
</ol>
|
||||
|
||||
<footer>This document reflects BAL plugin v0.4.7. Behaviour is derived directly from
|
||||
<footer>This document reflects BAL plugin v0.7.0. Behaviour is derived directly from
|
||||
<code>core/will.py</code>, <code>core/heirs.py</code> and <code>gui/qt/window.py</code>.</footer>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -357,5 +357,5 @@ that limit.
|
||||
|
||||
---
|
||||
|
||||
*This document reflects the current BAL plugin (v0.6.1). Behaviour is derived
|
||||
*This document reflects the current BAL plugin (v0.7.0). Behaviour is derived
|
||||
directly from `core/will.py`, `core/heirs.py` and `gui/qt/window.py`.*
|
||||
|
||||
@@ -475,6 +475,27 @@ transactions can have in the WILL tab, on each will‑executor that is online.
|
||||
> **NB:** When you close Electrum, the plugin automatically proceeds to execute
|
||||
> **Prepare → Sign → Broadcast** (if they have not already been completed) to
|
||||
> ensure the inheritance is correctly executed.
|
||||
>
|
||||
> Optionally, the **Rebuild on close** setting (available in **Tools → Plugins →
|
||||
> BAL**, default OFF) skips the full wizard and runs a one-shot rebuild/sign/push
|
||||
> flow when Electrum closes.
|
||||
|
||||
---
|
||||
|
||||
## Auto-rebuild on new transactions
|
||||
|
||||
> **NB:** this feature requires the **Auto-rebuild** setting to be enabled
|
||||
> (available in **Tools → Plugins → BAL**, default OFF).
|
||||
|
||||
When the **Auto-rebuild** setting is enabled, the plugin automatically rebuilds
|
||||
the will when new transactions are detected in the wallet (e.g. incoming
|
||||
payments). The delivery date is anticipated by one day so the new will orphans
|
||||
the old one on-chain without requiring a manual invalidation. An on-chain
|
||||
invalidation is only needed when the anticipated locktime crosses the **Check
|
||||
Alive** threshold (ADVANCED mode only).
|
||||
|
||||
This is useful for wallets that receive funds regularly: the inheritance stays
|
||||
up-to-date without manual intervention.
|
||||
|
||||
---
|
||||
|
||||
@@ -527,6 +548,32 @@ value.
|
||||
|
||||
---
|
||||
|
||||
## Command-line / headless usage
|
||||
|
||||
BAL can also be used without the Qt GUI, via Electrum's daemon mode. This is
|
||||
useful for scripting, automation, or running on a headless server.
|
||||
|
||||
**Prerequisites:** an Electrum daemon (`electrum daemon -d`) and a loaded wallet
|
||||
(`electrum load_wallet`).
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
electrum daemon -d
|
||||
electrum load_wallet
|
||||
electrum bal_heirs_list
|
||||
electrum bal_will_prepare
|
||||
electrum bal_will_sign --password '...'
|
||||
electrum bal_will_broadcast
|
||||
electrum stop
|
||||
```
|
||||
|
||||
All GUI operations (prepare, sign, broadcast, check, rebuild) are available as
|
||||
`bal_*` commands. See the full command table in the
|
||||
[README](../../README.md#command-line--headless-usage).
|
||||
|
||||
---
|
||||
|
||||
About installing a will‑executor server or collaboration, send your request to:
|
||||
**info@bitcoin-after.life**
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ select =["E", "W", "F", "I", "N", "B"]
|
||||
ignore = ["E501"]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"bal/gui/qt/*.py" = ["F403", "F405"] # intentional `from .common import *` hub
|
||||
"bal/gui/qt/common.py" = ["F401"] # re-exports consumed via `import *`
|
||||
"bal/gui/qt/common.py" = ["F401"] # re-exports consumed via explicit imports
|
||||
"bal/gui/qt/dialogs.py" = ["N802"] # Qt overrides: closeEvent/hideEvent/getText
|
||||
"bal/gui/qt/lists.py" = ["N802"] # Qt overrides: createEditor/setEditorData/setModelData
|
||||
"bal/gui/qt/widgets.py" = ["N802", "N815"] # Qt overrides + Qt signal attrs (valueChanged, ...)
|
||||
|
||||
10606
tests/karen7
10606
tests/karen7
File diff suppressed because one or more lines are too long
@@ -61,14 +61,14 @@ def test_advanced_mode_uses_threshold_absolute():
|
||||
def test_advanced_mode_parses_relative_threshold():
|
||||
# A relative threshold means "N days BEFORE the delivery": it resolves
|
||||
# against the stored locktime (backwards), not forward from now.
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
fake_now = 1_800_000_000.0
|
||||
locktime = fake_now + 90 * 86400
|
||||
settings = {"threshold": "30d", "locktime": locktime}
|
||||
result = resolve_date_to_check(False, settings, now=fake_now)
|
||||
# date_to_check = (locktime, midnight-normalised) - 30 days.
|
||||
expected = (datetime.fromtimestamp(locktime)
|
||||
expected = (datetime.fromtimestamp(locktime, tz=timezone.utc)
|
||||
.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
- timedelta(days=30)).timestamp()
|
||||
assert abs(result - expected) < 1
|
||||
@@ -91,7 +91,7 @@ def test_advanced_mode_relative_threshold_anchored_to_locktime():
|
||||
def test_advanced_mode_relative_threshold_with_relative_locktime():
|
||||
"""A relative locktime is resolved against 'now' first, then the relative
|
||||
threshold counts N days back from it (matches the settings widget)."""
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from bal.core.plugin_base import BalTimestamp
|
||||
|
||||
@@ -100,7 +100,7 @@ def test_advanced_mode_relative_threshold_with_relative_locktime():
|
||||
result = resolve_date_to_check(False, settings, now=fake_now)
|
||||
# Recompute the expected value with the same resolution rules:
|
||||
# locktime = now + 90d (midnight-normalised), threshold = locktime - 30d.
|
||||
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now))
|
||||
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now, tz=timezone.utc))
|
||||
expected = BalTimestamp("30d").to_date(locktime_dt, reverse=True).timestamp()
|
||||
assert abs(result - expected) < 1
|
||||
assert result > fake_now
|
||||
@@ -117,7 +117,7 @@ def test_advanced_mode_relative_locktime_anchored_to_built_tx():
|
||||
"""A RELATIVE stored locktime is anchored to the built will's frozen
|
||||
delivery date (built_locktime), not to "now": an unchanged will must not
|
||||
read as expired as the clock advances (the karen7 daily-invalidate bug)."""
|
||||
frozen = 1817438400 # frozen tx locktime (2027-08-05), built 2026-08-05
|
||||
frozen = 1817424000 # frozen tx locktime (2027-08-05 00:00 UTC), built 2026-08-05
|
||||
settings = {"threshold": "30d", "locktime": "2y"}
|
||||
# On build day the frozen delivery is authoritative: date_to_check is
|
||||
# frozen - 30d and NEVER drifts, however much later the clock gets.
|
||||
@@ -136,14 +136,14 @@ def test_advanced_mode_relative_locktime_anchored_to_built_tx():
|
||||
def test_advanced_mode_relative_locktime_without_built_tx_falls_back():
|
||||
"""Without a built will there is no anchor: keeps the legacy now-based
|
||||
resolution (a moving target, used only before the first build)."""
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from bal.core.plugin_base import BalTimestamp
|
||||
|
||||
fake_now = 1_800_000_000.0
|
||||
settings = {"threshold": "30d", "locktime": "90d"}
|
||||
result = resolve_date_to_check(False, settings, now=fake_now)
|
||||
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now))
|
||||
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now, tz=timezone.utc))
|
||||
expected = BalTimestamp("30d").to_date(locktime_dt, reverse=True).timestamp()
|
||||
assert abs(result - expected) < 1
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ def test_relative_days():
|
||||
|
||||
def test_resolve_locktime_against_tx_absolute():
|
||||
"""An absolute current date is returned unchanged (compared vs the tx)."""
|
||||
frozen = 1817438400
|
||||
frozen = 1817424000
|
||||
assert Util.resolve_locktime_against_tx(str(frozen), "1y", frozen) == frozen
|
||||
assert Util.resolve_locktime_against_tx(frozen, str(frozen), frozen) == frozen
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_resolve_locktime_against_tx_absolute():
|
||||
def test_resolve_locktime_against_tx_unchanged_relative():
|
||||
"""An unchanged relative recipe resolves to exactly the frozen tx locktime
|
||||
(coherent), instead of drifting one day per day away from it."""
|
||||
frozen = 1817438400 # 2027-08-05, i.e. a tx built 2026-08-05 with "1y"
|
||||
frozen = 1817424000 # 2027-08-05 00:00 UTC, i.e. a tx built 2026-08-05 with "1y"
|
||||
resolved = Util.resolve_locktime_against_tx("1y", "1y", frozen)
|
||||
assert resolved == frozen
|
||||
|
||||
@@ -113,7 +113,7 @@ def test_resolve_locktime_against_tx_unchanged_relative():
|
||||
def test_resolve_locktime_against_tx_lengthened():
|
||||
"""A lengthened relative recipe resolves later than the frozen tx locktime
|
||||
(this is what the postpone check uses to trigger invalidation)."""
|
||||
frozen = 1817438400 # tx built 2026-08-05 with "1y" -> delivery 2027-08-05
|
||||
frozen = 1817424000 # tx built 2026-08-05 with "1y" -> delivery 2027-08-05
|
||||
resolved = Util.resolve_locktime_against_tx("2y", "1y", frozen)
|
||||
assert resolved == frozen + 365 * 86400
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_resolve_locktime_against_tx_lengthened():
|
||||
def test_resolve_locktime_against_tx_shortened():
|
||||
"""A shortened relative recipe resolves earlier than the frozen tx locktime
|
||||
(this is what the anticipate/rebuild path uses)."""
|
||||
frozen = 1817438400
|
||||
frozen = 1817424000
|
||||
resolved = Util.resolve_locktime_against_tx("30d", "1y", frozen)
|
||||
assert resolved < frozen
|
||||
|
||||
@@ -129,7 +129,7 @@ def test_resolve_locktime_against_tx_shortened():
|
||||
def test_resolve_locktime_against_tx_no_relative_anchor():
|
||||
"""When the built recipe was absolute there is no anchor: falls back to the
|
||||
legacy forward-from-now resolution (returns a timestamp, no crash)."""
|
||||
frozen = 1817438400
|
||||
frozen = 1817424000
|
||||
result = Util.resolve_locktime_against_tx("30d", str(frozen), frozen)
|
||||
assert isinstance(result, int)
|
||||
assert result > 1700000000
|
||||
@@ -319,27 +319,6 @@ def test_anticipate_locktime():
|
||||
assert low >= 1
|
||||
|
||||
|
||||
def test_cmp_locktime():
|
||||
assert Util.cmp_locktime("30d", "30d") == 0
|
||||
# Note: cmp_locktime may return nonzero or None for mismatched units
|
||||
|
||||
|
||||
def test_get_locktimes():
|
||||
class FakeTx:
|
||||
locktime = 1700000000
|
||||
|
||||
# will with single entry
|
||||
will = {
|
||||
"tx1": {"tx": FakeTx()},
|
||||
}
|
||||
locktimes = list(Util.get_locktimes(will))
|
||||
assert 1700000000 in locktimes
|
||||
assert len(locktimes) == 1
|
||||
|
||||
# empty will
|
||||
assert list(Util.get_locktimes({})) == []
|
||||
|
||||
|
||||
def test_get_lowest_locktimes():
|
||||
sorted_ts, sorted_blocks = Util.get_lowest_locktimes([500000, 1700000000, 100, 900000])
|
||||
# 500000, 900000 are block-height (< THRESHOLD)
|
||||
@@ -351,18 +330,6 @@ def test_get_lowest_locktimes():
|
||||
assert Util.get_lowest_locktimes([]) == ([], [])
|
||||
|
||||
|
||||
def test_get_will_spent_utxos():
|
||||
class FakeTx:
|
||||
def inputs(self): return [1, 2, 3]
|
||||
|
||||
will = {
|
||||
"tx1": {"tx": FakeTx()},
|
||||
"tx2": {"tx": FakeTx()},
|
||||
}
|
||||
utxos = Util.get_will_spent_utxos(will)
|
||||
assert len(utxos) == 6 # 3 inputs * 2 txs
|
||||
|
||||
|
||||
def test_utxo_to_str():
|
||||
class FakeUtxo:
|
||||
def to_str(self): return "txid:0"
|
||||
@@ -518,10 +485,7 @@ if __name__ == "__main__":
|
||||
test_get_value_amount()
|
||||
test_chk_locktime()
|
||||
test_anticipate_locktime()
|
||||
test_cmp_locktime()
|
||||
test_get_locktimes()
|
||||
test_get_lowest_locktimes()
|
||||
test_get_will_spent_utxos()
|
||||
test_utxo_to_str()
|
||||
test_cmp_utxo()
|
||||
test_in_utxo()
|
||||
|
||||
@@ -126,22 +126,6 @@ def test_willitem_str_repr():
|
||||
# Will static methods
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_will_get_sorted_will():
|
||||
# Use a simple dict structure that will[key]["tx"].locktime works
|
||||
class FakeTx:
|
||||
def __init__(self, locktime):
|
||||
self.locktime = locktime
|
||||
|
||||
will = {
|
||||
"b": {"tx": FakeTx(200)},
|
||||
"a": {"tx": FakeTx(100)},
|
||||
}
|
||||
sorted_will = Will.get_sorted_will(will)
|
||||
assert len(sorted_will) == 2
|
||||
assert sorted_will[0][1]["tx"].locktime == 100
|
||||
assert sorted_will[1][1]["tx"].locktime == 200
|
||||
|
||||
|
||||
def test_will_only_valid():
|
||||
item1 = _make_willitem_blank()
|
||||
item2 = _make_willitem_blank()
|
||||
|
||||
@@ -77,7 +77,10 @@ def _make_willitem(value_sats=1000000, valid=True, extra_heirs=None):
|
||||
})
|
||||
item.STATUS = copy.deepcopy(WillItem.STATUS_DEFAULT)
|
||||
# Set the input value so the balance calculation works.
|
||||
item.tx.inputs()[0]._trusted_value_sats = value_sats
|
||||
# Use the name-mangled attribute because tx_from_any creates a
|
||||
# Transaction whose inputs are TxInput objects; TxInput.value_sats()
|
||||
# reads __value_sats, not _trusted_value_sats.
|
||||
item.tx.inputs()[0]._TxInput__value_sats = value_sats
|
||||
if not valid:
|
||||
item.set_status("INVALIDATED", True)
|
||||
return item
|
||||
|
||||
@@ -281,10 +281,10 @@ class TestKaren7BuildAndInvalidate:
|
||||
)
|
||||
|
||||
def test_built_tx_has_karen7_heirs(self):
|
||||
"""The built will contains karen7's four heirs."""
|
||||
assert len(self.heirs_model) == 4
|
||||
"""The built will contains karen7's six heirs."""
|
||||
assert len(self.heirs_model) == 6
|
||||
assert list(self.heirs_model.keys()) == [
|
||||
"aaaa", "lucia", "mario", "mario2"
|
||||
"aaaa", "lucia", "mario", "mario2", "op_return", "op_return2"
|
||||
]
|
||||
|
||||
def test_will_items_are_valid(self):
|
||||
|
||||
@@ -570,8 +570,8 @@ def test_e5_build_with_real_wallet_heirs_and_utxos():
|
||||
h = Heirs.__new__(Heirs)
|
||||
h.update(heirs_data)
|
||||
|
||||
assert len(h) == 4, f"expected 4 heirs, got {len(h)}"
|
||||
assert list(h.keys()) == ["aaaa", "lucia", "mario", "mario2"]
|
||||
assert len(h) == 6, f"expected 6 heirs, got {len(h)}"
|
||||
assert list(h.keys()) == ["aaaa", "lucia", "mario", "mario2", "op_return", "op_return2"]
|
||||
|
||||
# Mock the Electrum-heavy parts so the build can run in a test context.
|
||||
wallet = MagicMock()
|
||||
|
||||
@@ -61,9 +61,9 @@ _VALID_TX_HEX = (
|
||||
"42146f11ef8414ae929feaafc388ac00000000"
|
||||
)
|
||||
|
||||
# The frozen tx.locktime of karen7's valid item: delivery 2027-08-05, i.e. a
|
||||
# will built 2026-08-05 with a "1y" recipe.
|
||||
_FROZEN = 1817438400
|
||||
# The frozen tx.locktime of karen7's valid item: delivery 2027-08-05 00:00 UTC,
|
||||
# i.e. a will built 2026-08-05 with a "1y" recipe.
|
||||
_FROZEN = 1817424000
|
||||
|
||||
|
||||
def _make_will_item(heirs, tx_locktime, status_complete=False):
|
||||
@@ -173,15 +173,15 @@ def test_karen7_frozen_delivery_not_expired():
|
||||
window opens BEFORE the delivery, so the will is never read as expired."""
|
||||
data = _load_karen7()
|
||||
will_settings = data["will_settings"]
|
||||
valid_wid = "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8"
|
||||
valid_wid = "28b64bfd83878d15c668473aa695a2b9bc23196bc61ab3149e8f33241826978d"
|
||||
wi = WillItem(data["will"][valid_wid], _id=valid_wid)
|
||||
built_locktime = Will.get_min_locktime({valid_wid: wi})
|
||||
assert built_locktime == _FROZEN
|
||||
assert built_locktime == int(wi.tx.locktime)
|
||||
|
||||
date_to_check = resolve_date_to_check(
|
||||
False, will_settings, now=1_800_000_000.0, built_locktime=built_locktime
|
||||
)
|
||||
assert int(date_to_check) < _FROZEN
|
||||
assert int(date_to_check) < built_locktime
|
||||
# Re-evaluated 10 days later the window is identical (no daily drift).
|
||||
later = resolve_date_to_check(
|
||||
False, will_settings, now=1_800_000_000.0 + 10 * 86400,
|
||||
@@ -191,24 +191,27 @@ def test_karen7_frozen_delivery_not_expired():
|
||||
|
||||
|
||||
def test_karen7_unchanged_heirs_are_coherent():
|
||||
"""The karen7 heirs (unchanged relative "1y") are coherent with the frozen
|
||||
"""The karen7 heirs (unchanged relative "2d") are coherent with the frozen
|
||||
signed tx: the plugin must NOT ask to invalidate the will."""
|
||||
data = _load_karen7()
|
||||
valid_wid = "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8"
|
||||
valid_wid = "28b64bfd83878d15c668473aa695a2b9bc23196bc61ab3149e8f33241826978d"
|
||||
wi = WillItem(data["will"][valid_wid], _id=valid_wid)
|
||||
# Use _FROZEN (a UTC-midnight value) so the check is compatible with
|
||||
# the UTC anchoring code.
|
||||
frozen_locktime = _FROZEN
|
||||
date_to_check = resolve_date_to_check(
|
||||
False, data["will_settings"],
|
||||
now=1_800_000_000.0,
|
||||
built_locktime=int(wi.tx.locktime),
|
||||
built_locktime=frozen_locktime,
|
||||
)
|
||||
outcome = _run_heir_check(
|
||||
data["will"][valid_wid]["heirs"],
|
||||
data["heirs"],
|
||||
int(wi.tx.locktime),
|
||||
frozen_locktime,
|
||||
status_complete=True,
|
||||
)
|
||||
assert outcome.startswith("coherent"), outcome
|
||||
assert int(date_to_check) < int(wi.tx.locktime)
|
||||
assert int(date_to_check) < frozen_locktime
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@@ -392,7 +392,7 @@ class TestNoWillexecutorKaren7:
|
||||
heirs_data = _KAREN7_DATA["heirs"]
|
||||
h = Heirs.__new__(Heirs)
|
||||
h.update(heirs_data)
|
||||
assert len(h) == 4
|
||||
assert len(h) == 6
|
||||
|
||||
self.heirs_obj = h
|
||||
self.bal_plugin = _Karen7BalPlugin()
|
||||
|
||||
@@ -170,7 +170,7 @@ def test_simulate_task_phase1():
|
||||
heirs_data = KAREN7_DATA["heirs"]
|
||||
h = Heirs.__new__(Heirs)
|
||||
h.update(heirs_data)
|
||||
assert len(h) == 4
|
||||
assert len(h) == 6
|
||||
|
||||
# 2. Build UTXOs
|
||||
utxos = build_utxos(KAREN7_DATA)
|
||||
|
||||
111
tests/wallet_1
Normal file
111
tests/wallet_1
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"addr_history": {
|
||||
"bcrt1q0gn4ynf73tatc6ympqtgzny5e5klnrg58vndzz": [],
|
||||
"bcrt1q2dr55gr8203yvttc3pjnjwpu092nhfge34hpzm": [],
|
||||
"bcrt1q3xjtqf8dp27hth29ckx4es9megfhaaywekrqe7": [],
|
||||
"bcrt1q4wyxvjdehvs6vqd28zj3hgnkez54fzqg4tztau": [],
|
||||
"bcrt1q532ph3hdff30nptwna8zjhu52mvu7t5746m929": [],
|
||||
"bcrt1q57d2m8gz3h8kxtqyuvn8x0mymwe8g4p57cruzz": [],
|
||||
"bcrt1q5e7anlx4w365a7e5lzmqtejckdda542w80qat6": [],
|
||||
"bcrt1q5g2cd8enqkx4kspxs6jjtgcrhg0d9kgyeur5jv": [],
|
||||
"bcrt1q62v5mfk2wq4w2kls2am7kx2nxckg23l04jqn45": [],
|
||||
"bcrt1q7nau9g9xalgwp9usk7wdwmx8rjr89a6fqksr7t": [],
|
||||
"bcrt1q8r2e9hw5hm5869ga5c83dhzmz7g0k60kpndcwv": [],
|
||||
"bcrt1q94y88ezgftms2gj0sa3crulmp3h4hgvc22f65j": [],
|
||||
"bcrt1qa5u702rh8hxx9xrwqhrpmxvns2umhy9z8s68vt": [],
|
||||
"bcrt1qat47fmg4pd2usd4v85ufg0x86mhkl7kvvwtn59": [],
|
||||
"bcrt1qataakmm8g7w25jzzc4xv4humye4zdpasf7xcu6": [],
|
||||
"bcrt1qazgw97cjwezwsapgffm3wa6hu25cuw77fhs52n": [],
|
||||
"bcrt1qcxs3epqn6fjrr46tglnd6q7l5knyxe8yw66e34": [],
|
||||
"bcrt1qea6p44yl5dtfvvmc0ac9yf2j8dx2dwxnr727yg": [],
|
||||
"bcrt1qeacnncgutxf0qyu30ja4hqur48tgw5e4wdgrn9": [],
|
||||
"bcrt1qedmkz0c36sw349m52l0v6n6kxgm8esjetcr75f": [],
|
||||
"bcrt1qgn67p03qmpceae4nf0aep9vqlrkp2kgny0cnc5": [],
|
||||
"bcrt1qhax23np75xvs3rtcqpur8xzh66f46xlzdrz70y": [],
|
||||
"bcrt1qn6w6ge7u3dj8v8swjjkrf9unqsend4xm4fyny0": [],
|
||||
"bcrt1qnckdaqxu6qkdwmfnyy2eyan4ehcrz7t6t8tgrk": [],
|
||||
"bcrt1qqj08edl8hy2umy4yan65mhyxjfptwfqzc5qs22": [],
|
||||
"bcrt1qrcx8trhqxu7w6yst54gzfuk0357pyhdfu2wjan": [],
|
||||
"bcrt1qspjsrtdtpmkaskhmtjeg773ydru0w66gvch98d": [],
|
||||
"bcrt1qsxl4g5ue8fu44rua4r3ldathjkdnvg8a0y7me5": [],
|
||||
"bcrt1qvxe9qk0zafy4tva3kr9uhre3njj8asqevt2mvf": [],
|
||||
"bcrt1qw4s84rf6p7e6kzxrcxckeexksyn8za6nah7ztj": []
|
||||
},
|
||||
"addresses": {
|
||||
"change": [
|
||||
"bcrt1qgn67p03qmpceae4nf0aep9vqlrkp2kgny0cnc5",
|
||||
"bcrt1qvxe9qk0zafy4tva3kr9uhre3njj8asqevt2mvf",
|
||||
"bcrt1qedmkz0c36sw349m52l0v6n6kxgm8esjetcr75f",
|
||||
"bcrt1qrcx8trhqxu7w6yst54gzfuk0357pyhdfu2wjan",
|
||||
"bcrt1qa5u702rh8hxx9xrwqhrpmxvns2umhy9z8s68vt",
|
||||
"bcrt1qqj08edl8hy2umy4yan65mhyxjfptwfqzc5qs22",
|
||||
"bcrt1q57d2m8gz3h8kxtqyuvn8x0mymwe8g4p57cruzz",
|
||||
"bcrt1qataakmm8g7w25jzzc4xv4humye4zdpasf7xcu6",
|
||||
"bcrt1q7nau9g9xalgwp9usk7wdwmx8rjr89a6fqksr7t",
|
||||
"bcrt1qsxl4g5ue8fu44rua4r3ldathjkdnvg8a0y7me5"
|
||||
],
|
||||
"receiving": [
|
||||
"bcrt1qeacnncgutxf0qyu30ja4hqur48tgw5e4wdgrn9",
|
||||
"bcrt1q532ph3hdff30nptwna8zjhu52mvu7t5746m929",
|
||||
"bcrt1q62v5mfk2wq4w2kls2am7kx2nxckg23l04jqn45",
|
||||
"bcrt1qcxs3epqn6fjrr46tglnd6q7l5knyxe8yw66e34",
|
||||
"bcrt1q4wyxvjdehvs6vqd28zj3hgnkez54fzqg4tztau",
|
||||
"bcrt1qspjsrtdtpmkaskhmtjeg773ydru0w66gvch98d",
|
||||
"bcrt1q2dr55gr8203yvttc3pjnjwpu092nhfge34hpzm",
|
||||
"bcrt1qea6p44yl5dtfvvmc0ac9yf2j8dx2dwxnr727yg",
|
||||
"bcrt1q8r2e9hw5hm5869ga5c83dhzmz7g0k60kpndcwv",
|
||||
"bcrt1q94y88ezgftms2gj0sa3crulmp3h4hgvc22f65j",
|
||||
"bcrt1qn6w6ge7u3dj8v8swjjkrf9unqsend4xm4fyny0",
|
||||
"bcrt1qw4s84rf6p7e6kzxrcxckeexksyn8za6nah7ztj",
|
||||
"bcrt1qat47fmg4pd2usd4v85ufg0x86mhkl7kvvwtn59",
|
||||
"bcrt1qazgw97cjwezwsapgffm3wa6hu25cuw77fhs52n",
|
||||
"bcrt1q5e7anlx4w365a7e5lzmqtejckdda542w80qat6",
|
||||
"bcrt1q3xjtqf8dp27hth29ckx4es9megfhaaywekrqe7",
|
||||
"bcrt1qhax23np75xvs3rtcqpur8xzh66f46xlzdrz70y",
|
||||
"bcrt1q5g2cd8enqkx4kspxs6jjtgcrhg0d9kgyeur5jv",
|
||||
"bcrt1q0gn4ynf73tatc6ympqtgzny5e5klnrg58vndzz",
|
||||
"bcrt1qnckdaqxu6qkdwmfnyy2eyan4ehcrz7t6t8tgrk"
|
||||
]
|
||||
},
|
||||
"db_metadata": {
|
||||
"creation_timestamp": 1787164437,
|
||||
"first_electrum_version_used": "4.8.1"
|
||||
},
|
||||
"fiat_value": {},
|
||||
"frozen_coins": {},
|
||||
"genesis_blockhash": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
||||
"invoices": {},
|
||||
"keystore": {
|
||||
"derivation": "m/84h/1h/0h",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "98d38daf",
|
||||
"type": "bip32",
|
||||
"xprv": "vprv9LqKDMUNWwFEkvpapGse6Ar5pBEUkmzYvhpeNAKZCJGKMntYQ297MQJYqeQH7BCCAht3CkvaG3GubMfLe6AoHQs44jomgdZktncvAQtdAPL",
|
||||
"xpub": "vpub5Zpfcs1GMJoXyQu3vJQeTJnpND4yAEiQHvkFAYjAkdoJEbDgwZTMuCd2gutewx8c2bPcFPNY1cHvQkC6NRCV2SYD5xFWvgF92iH3NTwTY6T"
|
||||
},
|
||||
"labels": {},
|
||||
"notes_text": "",
|
||||
"num_parents": {},
|
||||
"payment_requests": {},
|
||||
"plugin_data": {},
|
||||
"prevouts_by_scripthash": {},
|
||||
"qt-console-history": [],
|
||||
"seed_version": 71,
|
||||
"spent_outpoints": {},
|
||||
"stored_height": 344,
|
||||
"transactions": {},
|
||||
"tx_batches": {},
|
||||
"tx_fees": {},
|
||||
"txi": {},
|
||||
"txo": {},
|
||||
"use_encryption": false,
|
||||
"verified_tx3": {},
|
||||
"wallet_type": "standard",
|
||||
"will": {},
|
||||
"winpos-qt": [
|
||||
100,
|
||||
169,
|
||||
640,
|
||||
400
|
||||
]
|
||||
}
|
||||
111
tests/wallet_10
Normal file
111
tests/wallet_10
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"addr_history": {
|
||||
"bcrt1q03dclsgjkxm7kd250y06zcqdzdpfa4pfm4wzyh": [],
|
||||
"bcrt1q0d3ecwnayn0vryw9zx0rde68ruk9sgnt7ugprk": [],
|
||||
"bcrt1q26msev2ghjgedu4anspjl2jsmtym0m3jlqv9el": [],
|
||||
"bcrt1q47fp4klegcmvj6dpzkqlvddhk0hu2udlnnfq78": [],
|
||||
"bcrt1q5ntujhaq5h8xyg25ed84ymfcnus0t5at8mehwz": [],
|
||||
"bcrt1q6t0qsg6u3pj068rv0zkl8n4gmkvamd5zlrssf4": [],
|
||||
"bcrt1q8hc6fmau495rf4crx0v76cmz9t9ymzu0qh54ke": [],
|
||||
"bcrt1qc9h3gmeqryu6s97p65x9c443uj0kyr34e3849y": [],
|
||||
"bcrt1qcmm22dlt0g580j6rwcs0rlx7pnvjhsn9sek6c9": [],
|
||||
"bcrt1qdxeq8r8lnn2sxhzv5umam4jxrpw0w9nf2s3ara": [],
|
||||
"bcrt1qe80p3pukhtxyhdhv9e8yve5a73d07xd9jvsr0n": [],
|
||||
"bcrt1qec2ykz5zr6r93rh5f4e738c0qrtxkgmf3pzdhd": [],
|
||||
"bcrt1qg8sy94c7wsstwpzzcyqr2refdeqcms44qe90re": [],
|
||||
"bcrt1qgpvnddkqsrjghuqdjuqsjkj94cszzjjf26zdaz": [],
|
||||
"bcrt1qjeq5v3y86tv6un6qqgagqx06w6kxrpw97hay5m": [],
|
||||
"bcrt1qjkyp3kehcgyhh7tmpcyqyyeue38qmg6nv2nznp": [],
|
||||
"bcrt1qjy80h295jrksmwwfj5f6y2yvx09ddrdltdmvx4": [],
|
||||
"bcrt1qk7uj3c82ygm0wd4jcdlge747qxxpkarasc30fh": [],
|
||||
"bcrt1qlgwhtdrqgejl7tkejhnmck624s8x3rkghejz7h": [],
|
||||
"bcrt1qm6gvrddpmswtjj67h36hxed48czamgdct2wy8f": [],
|
||||
"bcrt1qpjq8hl6ndtn9lsx89z8rfj3uhzsdg9pj562rlx": [],
|
||||
"bcrt1qqv0ck9gd78a8mqazgffkkswra0wlejehsrz7g6": [],
|
||||
"bcrt1qrn8rgnr55w967ucgk555p60whs83zmxp7xvv3n": [],
|
||||
"bcrt1qvmtpxpnqc58xq8u78w87mf7lpt6j7t5l05l5pn": [],
|
||||
"bcrt1qwelyukcq0yzcty6d9srn4e4lnehfrqu4hr2k90": [],
|
||||
"bcrt1qwk25c5tes6he32vdszhpkl0jt89xa55yv3n4jj": [],
|
||||
"bcrt1qyr94w9pls4klsacnzpwvanq7yzpzdcgc2k8q93": [],
|
||||
"bcrt1qyxmzrv7n7guj23ezj4t3avc7vr9par5k2sdhyy": [],
|
||||
"bcrt1qz0wnyeuuya33mlk398urpcq3hkl3dqxua5wnv3": [],
|
||||
"bcrt1qz8w9n2z6wfpxyfx5txp5nke7043kapg4mtjmqe": []
|
||||
},
|
||||
"addresses": {
|
||||
"change": [
|
||||
"bcrt1qjeq5v3y86tv6un6qqgagqx06w6kxrpw97hay5m",
|
||||
"bcrt1qg8sy94c7wsstwpzzcyqr2refdeqcms44qe90re",
|
||||
"bcrt1qrn8rgnr55w967ucgk555p60whs83zmxp7xvv3n",
|
||||
"bcrt1qz0wnyeuuya33mlk398urpcq3hkl3dqxua5wnv3",
|
||||
"bcrt1qdxeq8r8lnn2sxhzv5umam4jxrpw0w9nf2s3ara",
|
||||
"bcrt1q8hc6fmau495rf4crx0v76cmz9t9ymzu0qh54ke",
|
||||
"bcrt1q5ntujhaq5h8xyg25ed84ymfcnus0t5at8mehwz",
|
||||
"bcrt1qpjq8hl6ndtn9lsx89z8rfj3uhzsdg9pj562rlx",
|
||||
"bcrt1qjkyp3kehcgyhh7tmpcyqyyeue38qmg6nv2nznp",
|
||||
"bcrt1qc9h3gmeqryu6s97p65x9c443uj0kyr34e3849y"
|
||||
],
|
||||
"receiving": [
|
||||
"bcrt1qyr94w9pls4klsacnzpwvanq7yzpzdcgc2k8q93",
|
||||
"bcrt1q6t0qsg6u3pj068rv0zkl8n4gmkvamd5zlrssf4",
|
||||
"bcrt1qqv0ck9gd78a8mqazgffkkswra0wlejehsrz7g6",
|
||||
"bcrt1qwelyukcq0yzcty6d9srn4e4lnehfrqu4hr2k90",
|
||||
"bcrt1q47fp4klegcmvj6dpzkqlvddhk0hu2udlnnfq78",
|
||||
"bcrt1qm6gvrddpmswtjj67h36hxed48czamgdct2wy8f",
|
||||
"bcrt1qe80p3pukhtxyhdhv9e8yve5a73d07xd9jvsr0n",
|
||||
"bcrt1qlgwhtdrqgejl7tkejhnmck624s8x3rkghejz7h",
|
||||
"bcrt1qjy80h295jrksmwwfj5f6y2yvx09ddrdltdmvx4",
|
||||
"bcrt1qwk25c5tes6he32vdszhpkl0jt89xa55yv3n4jj",
|
||||
"bcrt1qgpvnddkqsrjghuqdjuqsjkj94cszzjjf26zdaz",
|
||||
"bcrt1qz8w9n2z6wfpxyfx5txp5nke7043kapg4mtjmqe",
|
||||
"bcrt1q0d3ecwnayn0vryw9zx0rde68ruk9sgnt7ugprk",
|
||||
"bcrt1qyxmzrv7n7guj23ezj4t3avc7vr9par5k2sdhyy",
|
||||
"bcrt1qk7uj3c82ygm0wd4jcdlge747qxxpkarasc30fh",
|
||||
"bcrt1q03dclsgjkxm7kd250y06zcqdzdpfa4pfm4wzyh",
|
||||
"bcrt1q26msev2ghjgedu4anspjl2jsmtym0m3jlqv9el",
|
||||
"bcrt1qvmtpxpnqc58xq8u78w87mf7lpt6j7t5l05l5pn",
|
||||
"bcrt1qec2ykz5zr6r93rh5f4e738c0qrtxkgmf3pzdhd",
|
||||
"bcrt1qcmm22dlt0g580j6rwcs0rlx7pnvjhsn9sek6c9"
|
||||
]
|
||||
},
|
||||
"db_metadata": {
|
||||
"creation_timestamp": 1786882913,
|
||||
"first_electrum_version_used": "4.8.0"
|
||||
},
|
||||
"fiat_value": {},
|
||||
"frozen_coins": {},
|
||||
"genesis_blockhash": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
||||
"invoices": {},
|
||||
"keystore": {
|
||||
"derivation": "m/84h/1h/0h",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "0cf07337",
|
||||
"type": "bip32",
|
||||
"xprv": "vprv9LcbaH8mg7DeGSvtncGaMmtdw1kN8xo6smsibU9iLjJDvGFnjsZcSUq47D5hBRHFjP1kqiVKi68q43m5DAuAYWuEoanC5vM5WFqDuZtMgiP",
|
||||
"xpub": "vpub5ZbwynffWUmwUw1MtdoaiuqNV3arYRWxEzoKPrZKu4qCo4awHQsrzH9XxVQy3LZLpMAyezD1VE2gnSqNyH64asAZ8RX7eky31uUA1ZLuQMS"
|
||||
},
|
||||
"labels": {},
|
||||
"notes_text": "",
|
||||
"num_parents": {},
|
||||
"payment_requests": {},
|
||||
"plugin_data": {},
|
||||
"prevouts_by_scripthash": {},
|
||||
"qt-console-history": [],
|
||||
"seed_version": 71,
|
||||
"spent_outpoints": {},
|
||||
"stored_height": 2548,
|
||||
"transactions": {},
|
||||
"tx_batches": {},
|
||||
"tx_fees": {},
|
||||
"txi": {},
|
||||
"txo": {},
|
||||
"use_encryption": false,
|
||||
"verified_tx3": {},
|
||||
"wallet_type": "standard",
|
||||
"will": {},
|
||||
"winpos-qt": [
|
||||
100,
|
||||
100,
|
||||
840,
|
||||
400
|
||||
]
|
||||
}
|
||||
111
tests/wallet_11
Normal file
111
tests/wallet_11
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"addr_history": {
|
||||
"bcrt1q0hwph5shlxy0j2xj059xs8mtnszd4hzw72uh6y": [],
|
||||
"bcrt1q0jt7yl5vwp5e00hlfwzssaatvyzh83d8gz80yr": [],
|
||||
"bcrt1q243sh2sjppq9sczt6wmgw2kffzhyw96f387996": [],
|
||||
"bcrt1q3fxng5692thfjalg2ka3jwtkl5fy623nx42p5p": [],
|
||||
"bcrt1q3qqf94lu6k225u5fm22cwv2pfe7zrckvp0fxp5": [],
|
||||
"bcrt1q3wytvhxkqxwqjzrjjqdnpyqhuyr9jf3f0v2kec": [],
|
||||
"bcrt1q57rvd4hmzw2t7hg9w3phc6hv3m4y0wuuxkv5ul": [],
|
||||
"bcrt1q5qp2a6zkv3n2gq609mww0ksu7g3z6m7lr48wmq": [],
|
||||
"bcrt1q6pt9rtvd4l3jg5hqj5959y74hx464zz3dzct82": [],
|
||||
"bcrt1q70qemcx3xw9raeylkcyxlc02ae3f5gg730vc06": [],
|
||||
"bcrt1q7fwz30g78amf5j06nzznsm4cnwaw00adr5l2re": [],
|
||||
"bcrt1q9729qhnqg6w97l37lkapg840h9ynzjmnh39j7t": [],
|
||||
"bcrt1q9gr6nar56jl78w748hp8mlr04se3ht3a78k5t5": [],
|
||||
"bcrt1qceqt039gm8rt5n0jwzf0htpwmnu2ycaxfnpt02": [],
|
||||
"bcrt1qcjmvvwhh6y4qe7reeztfaq27ju30s8t8gdqql6": [],
|
||||
"bcrt1qk6uzn0spdawzccckuew9peaegrzh0w3fptzjr0": [],
|
||||
"bcrt1ql4v46wwluvc8eup6f5fmrgqhlq20kpsq2dyha2": [],
|
||||
"bcrt1qn8q4suwpktwmakfum2gqqhxqvjxw5f3095r2vz": [],
|
||||
"bcrt1qpj5g06p78g8avjrev4m9an93v7qtdstxrvmwry": [],
|
||||
"bcrt1qpx8lywstlp30currg0ajpj0ttcy9qle5q90f27": [],
|
||||
"bcrt1qqen4jdg7ytmx9quhelzxgly2w6s8pde0huq568": [],
|
||||
"bcrt1qrjurdspugevgrsdgdtrh6kn24nv64mfsaxfm2s": [],
|
||||
"bcrt1qrpqzd27s5jh7j59s9zma759q4rtpquurp3r6ms": [],
|
||||
"bcrt1qs6e7y7hzq7rpr4tc3cdk6p92elhul3wz8nkrv5": [],
|
||||
"bcrt1qth2mh23lv060q26p4ns3pmxjuszrntyqafhl8n": [],
|
||||
"bcrt1qtqsjpx23rtcvfdspjz0ss02r5dcyh975u40w99": [],
|
||||
"bcrt1quu69e4q08etxevlkkmd5v96q2y6edn6lvf0aup": [],
|
||||
"bcrt1qvwzgw0wgxdzrgkksn29j8uehugy7fxw5ykdsrr": [],
|
||||
"bcrt1qxe98rgw9v05qrjs6hfrr4m9wmgv5jdvztgys3z": [],
|
||||
"bcrt1qz56zhzduj9ucle87t6khnvmdgvcdsax7m8nly5": []
|
||||
},
|
||||
"addresses": {
|
||||
"change": [
|
||||
"bcrt1qk6uzn0spdawzccckuew9peaegrzh0w3fptzjr0",
|
||||
"bcrt1qz56zhzduj9ucle87t6khnvmdgvcdsax7m8nly5",
|
||||
"bcrt1qrjurdspugevgrsdgdtrh6kn24nv64mfsaxfm2s",
|
||||
"bcrt1qtqsjpx23rtcvfdspjz0ss02r5dcyh975u40w99",
|
||||
"bcrt1qth2mh23lv060q26p4ns3pmxjuszrntyqafhl8n",
|
||||
"bcrt1qrpqzd27s5jh7j59s9zma759q4rtpquurp3r6ms",
|
||||
"bcrt1qvwzgw0wgxdzrgkksn29j8uehugy7fxw5ykdsrr",
|
||||
"bcrt1q5qp2a6zkv3n2gq609mww0ksu7g3z6m7lr48wmq",
|
||||
"bcrt1q0jt7yl5vwp5e00hlfwzssaatvyzh83d8gz80yr",
|
||||
"bcrt1q57rvd4hmzw2t7hg9w3phc6hv3m4y0wuuxkv5ul"
|
||||
],
|
||||
"receiving": [
|
||||
"bcrt1ql4v46wwluvc8eup6f5fmrgqhlq20kpsq2dyha2",
|
||||
"bcrt1qs6e7y7hzq7rpr4tc3cdk6p92elhul3wz8nkrv5",
|
||||
"bcrt1q7fwz30g78amf5j06nzznsm4cnwaw00adr5l2re",
|
||||
"bcrt1qpj5g06p78g8avjrev4m9an93v7qtdstxrvmwry",
|
||||
"bcrt1q243sh2sjppq9sczt6wmgw2kffzhyw96f387996",
|
||||
"bcrt1q3fxng5692thfjalg2ka3jwtkl5fy623nx42p5p",
|
||||
"bcrt1qqen4jdg7ytmx9quhelzxgly2w6s8pde0huq568",
|
||||
"bcrt1qcjmvvwhh6y4qe7reeztfaq27ju30s8t8gdqql6",
|
||||
"bcrt1q3qqf94lu6k225u5fm22cwv2pfe7zrckvp0fxp5",
|
||||
"bcrt1qn8q4suwpktwmakfum2gqqhxqvjxw5f3095r2vz",
|
||||
"bcrt1q9729qhnqg6w97l37lkapg840h9ynzjmnh39j7t",
|
||||
"bcrt1q6pt9rtvd4l3jg5hqj5959y74hx464zz3dzct82",
|
||||
"bcrt1q70qemcx3xw9raeylkcyxlc02ae3f5gg730vc06",
|
||||
"bcrt1q9gr6nar56jl78w748hp8mlr04se3ht3a78k5t5",
|
||||
"bcrt1qceqt039gm8rt5n0jwzf0htpwmnu2ycaxfnpt02",
|
||||
"bcrt1qxe98rgw9v05qrjs6hfrr4m9wmgv5jdvztgys3z",
|
||||
"bcrt1q3wytvhxkqxwqjzrjjqdnpyqhuyr9jf3f0v2kec",
|
||||
"bcrt1qpx8lywstlp30currg0ajpj0ttcy9qle5q90f27",
|
||||
"bcrt1quu69e4q08etxevlkkmd5v96q2y6edn6lvf0aup",
|
||||
"bcrt1q0hwph5shlxy0j2xj059xs8mtnszd4hzw72uh6y"
|
||||
]
|
||||
},
|
||||
"db_metadata": {
|
||||
"creation_timestamp": 1786883730,
|
||||
"first_electrum_version_used": "4.8.0"
|
||||
},
|
||||
"fiat_value": {},
|
||||
"frozen_coins": {},
|
||||
"genesis_blockhash": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
||||
"invoices": {},
|
||||
"keystore": {
|
||||
"derivation": "m/84h/1h/0h",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "62f91f1a",
|
||||
"type": "bip32",
|
||||
"xprv": "vprv9KyNUQerxaL38fAA93TrQJLSpdGjBKVUq8F6QziiHea8ybzmpAPgbgHPaZ5gsksAdn9ZLVih11xTDGDWEqkZRzViY8SKZVPziAvEPPRNnGG",
|
||||
"xpub": "vpub5YxisvBknwtLM9EdF4zrmSHBNf7DanDLCMAhDP8Kqz77rQKvMhhw9UbsRnx11njgk6rcRZxnERmn2ZGC77VYyDosr9vLX5asfPEHBMbFDGu"
|
||||
},
|
||||
"labels": {},
|
||||
"notes_text": "",
|
||||
"num_parents": {},
|
||||
"payment_requests": {},
|
||||
"plugin_data": {},
|
||||
"prevouts_by_scripthash": {},
|
||||
"qt-console-history": [],
|
||||
"seed_version": 71,
|
||||
"spent_outpoints": {},
|
||||
"stored_height": 2552,
|
||||
"transactions": {},
|
||||
"tx_batches": {},
|
||||
"tx_fees": {},
|
||||
"txi": {},
|
||||
"txo": {},
|
||||
"use_encryption": false,
|
||||
"verified_tx3": {},
|
||||
"wallet_type": "standard",
|
||||
"will": {},
|
||||
"winpos-qt": [
|
||||
100,
|
||||
100,
|
||||
840,
|
||||
400
|
||||
]
|
||||
}
|
||||
125
tests/wallet_4
Normal file
125
tests/wallet_4
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"active_forwardings": {},
|
||||
"addr_history": {
|
||||
"bcrt1q0phrauahlhdaandkq7cp9dr4awrzav9zh8ylau": [],
|
||||
"bcrt1q2esfq5jlrj9nh5lj7kjxg736ksh8f3lsquuchp": [],
|
||||
"bcrt1q2kylshq4vm8r8kcpgdd65mw9w3wauz5j6c66lf": [],
|
||||
"bcrt1q2uhhatt83hyhu4vp045c0q22dkeuy3yufpk7tv": [],
|
||||
"bcrt1q7hward08lvu2s38paqkrws655xxfk608zef7rs": [],
|
||||
"bcrt1q8sathu6j8cqvmm6pcg6ezj9qrc528c9pdz8dg2": [],
|
||||
"bcrt1q9ulln7tlez5se4ydsamh3258ja3wmewm2hkat3": [],
|
||||
"bcrt1qad8qsgl5kwnjc2jh065f9xgkwrxrs6e4crj7eh": [],
|
||||
"bcrt1qayyhz99qmql6vmtzy2j9y8yc5umt3ap0clrr37": [],
|
||||
"bcrt1qc7pzldcrcw23ele4ruughm9u8dp3k6j7hmyl86": [],
|
||||
"bcrt1qcqkeapfkq42uxpdk58h7glqxjsj59c4rkus6jt": [],
|
||||
"bcrt1qcvyffjmdupj86vc49k238ehtvvng62hpfcudxm": [],
|
||||
"bcrt1qd6jksjh0uwaurl287jftdjg854g3c02zlw6gxw": [],
|
||||
"bcrt1qehrqjhn74x738580kfh2s9x7622j2mjvgukzzw": [],
|
||||
"bcrt1qfhxe8lgxvk5f7hce2rls2xg22yp2vwrqzktrqq": [],
|
||||
"bcrt1qhymanatzt7f90060zw7zunwuacn2suqs656a3r": [],
|
||||
"bcrt1qk7l2dxrgfzwcdf7mrhgewxupsv5yd0vv6j9v2g": [],
|
||||
"bcrt1qkwjlhz3xv5hsf0k5d8ntcgwvd2ulrrdmv4h9zd": [],
|
||||
"bcrt1qm4pkufqzezk4l8vjkdx53lh6n9qw8l9mk9fce0": [],
|
||||
"bcrt1qnpqjud06cerd0a5wxpxzk94lks47x576wd06dk": [],
|
||||
"bcrt1qpv4wfzlqejlxatq7nmn7cns5ce60xpnmf9mfkd": [],
|
||||
"bcrt1qt5890v7gts8g5k6n4zxayj7c0rr5pqyn6g5ku7": [],
|
||||
"bcrt1qtjp7sjxjghstj5hhggl4nrgea2wjh3clqctnh4": [],
|
||||
"bcrt1quvrjnrywcsrhhgqfwgvrg7htuh7lp9yc444cuw": [],
|
||||
"bcrt1qxzhk758r7uq4cnhe4av0fe6qwef8w245rdhuhf": [],
|
||||
"bcrt1qyfxfpvajske5t7urmqdyuysq2keghf820x7g3l": [],
|
||||
"bcrt1qynjsulpfhkpq2zj0dgwufva5xjxzm2g7ccu6wr": [],
|
||||
"bcrt1qyr4nek8u89d43y32tug8pdsgm7lpmrvpcd2rpv": [],
|
||||
"bcrt1qyytq83t4wds8v7mu9d7ncmvrkzhppfjdjktz64": [],
|
||||
"bcrt1qzvqkj2uh63psn0q5r67pyzs5kzxq7fc8tsucwk": []
|
||||
},
|
||||
"addresses": {
|
||||
"change": [
|
||||
"bcrt1q8sathu6j8cqvmm6pcg6ezj9qrc528c9pdz8dg2",
|
||||
"bcrt1qyytq83t4wds8v7mu9d7ncmvrkzhppfjdjktz64",
|
||||
"bcrt1qkwjlhz3xv5hsf0k5d8ntcgwvd2ulrrdmv4h9zd",
|
||||
"bcrt1qcvyffjmdupj86vc49k238ehtvvng62hpfcudxm",
|
||||
"bcrt1qad8qsgl5kwnjc2jh065f9xgkwrxrs6e4crj7eh",
|
||||
"bcrt1qhymanatzt7f90060zw7zunwuacn2suqs656a3r",
|
||||
"bcrt1qxzhk758r7uq4cnhe4av0fe6qwef8w245rdhuhf",
|
||||
"bcrt1q9ulln7tlez5se4ydsamh3258ja3wmewm2hkat3",
|
||||
"bcrt1q2kylshq4vm8r8kcpgdd65mw9w3wauz5j6c66lf",
|
||||
"bcrt1qt5890v7gts8g5k6n4zxayj7c0rr5pqyn6g5ku7"
|
||||
],
|
||||
"receiving": [
|
||||
"bcrt1qcqkeapfkq42uxpdk58h7glqxjsj59c4rkus6jt",
|
||||
"bcrt1qyr4nek8u89d43y32tug8pdsgm7lpmrvpcd2rpv",
|
||||
"bcrt1qayyhz99qmql6vmtzy2j9y8yc5umt3ap0clrr37",
|
||||
"bcrt1qfhxe8lgxvk5f7hce2rls2xg22yp2vwrqzktrqq",
|
||||
"bcrt1q7hward08lvu2s38paqkrws655xxfk608zef7rs",
|
||||
"bcrt1qk7l2dxrgfzwcdf7mrhgewxupsv5yd0vv6j9v2g",
|
||||
"bcrt1qyfxfpvajske5t7urmqdyuysq2keghf820x7g3l",
|
||||
"bcrt1qtjp7sjxjghstj5hhggl4nrgea2wjh3clqctnh4",
|
||||
"bcrt1qc7pzldcrcw23ele4ruughm9u8dp3k6j7hmyl86",
|
||||
"bcrt1qpv4wfzlqejlxatq7nmn7cns5ce60xpnmf9mfkd",
|
||||
"bcrt1qzvqkj2uh63psn0q5r67pyzs5kzxq7fc8tsucwk",
|
||||
"bcrt1qynjsulpfhkpq2zj0dgwufva5xjxzm2g7ccu6wr",
|
||||
"bcrt1q0phrauahlhdaandkq7cp9dr4awrzav9zh8ylau",
|
||||
"bcrt1qm4pkufqzezk4l8vjkdx53lh6n9qw8l9mk9fce0",
|
||||
"bcrt1qd6jksjh0uwaurl287jftdjg854g3c02zlw6gxw",
|
||||
"bcrt1qehrqjhn74x738580kfh2s9x7622j2mjvgukzzw",
|
||||
"bcrt1q2esfq5jlrj9nh5lj7kjxg736ksh8f3lsquuchp",
|
||||
"bcrt1qnpqjud06cerd0a5wxpxzk94lks47x576wd06dk",
|
||||
"bcrt1quvrjnrywcsrhhgqfwgvrg7htuh7lp9yc444cuw",
|
||||
"bcrt1q2uhhatt83hyhu4vp045c0q22dkeuy3yufpk7tv"
|
||||
]
|
||||
},
|
||||
"channels": {},
|
||||
"db_metadata": {
|
||||
"creation_timestamp": 1786881419,
|
||||
"first_electrum_version_used": "4.8.0"
|
||||
},
|
||||
"dont_expire_htlcs": {},
|
||||
"dont_settle_htlcs": {},
|
||||
"fiat_value": {},
|
||||
"forwarding_failures": {},
|
||||
"frozen_coins": {},
|
||||
"genesis_blockhash": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
||||
"imported_channel_backups": {},
|
||||
"invoices": {},
|
||||
"keystore": {
|
||||
"derivation": "m/0h",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "257f5dd3",
|
||||
"seed": "govern large reason sense game labor orbit swarm once wealth plunge sorry",
|
||||
"seed_type": "segwit",
|
||||
"type": "bip32",
|
||||
"xprv": "vprv9FWbyposPdHhxu62H8aPJqT9TwFpToJrosCueQ7EXtNe7Y9sWuYcVwwToS9hge5TM7BnJCiJxve2Vq98G9YRUXoq8AAT7tDPy7kzfhfDuG2",
|
||||
"xpub": "vpub5UVxPLLmDzr1BPAVPA7PfyPt1y6JsG2iB68WSnWr6DuczLV24Srs3kFwegso2hk1J9RvrjSQbC1otaCLrHbHP5EGqMteUi6m7DsPgGYXYrq"
|
||||
},
|
||||
"labels": {},
|
||||
"lightning_payments": {},
|
||||
"lightning_preimages": {},
|
||||
"lightning_xprv": "vprv9HGmVFxtePRhNodb3RNo1RgT3kT9GTtitf9KHg7FtYzZLBPimW23K34UqMLPKvnDqoqF9Yp5vWZyUfrpMW6SEySycPRqW6dPFxBmKawJg3g",
|
||||
"notes_text": "",
|
||||
"num_parents": {},
|
||||
"onchain_channel_backups": {},
|
||||
"payment_requests": {},
|
||||
"plugin_data": {},
|
||||
"prevouts_by_scripthash": {},
|
||||
"qt-console-history": [],
|
||||
"received_mpp_htlcs": {},
|
||||
"seed_version": 71,
|
||||
"spent_outpoints": {},
|
||||
"stored_height": 2545,
|
||||
"submarine_swaps": {},
|
||||
"transactions": {},
|
||||
"tx_batches": {},
|
||||
"tx_fees": {},
|
||||
"txi": {},
|
||||
"txo": {},
|
||||
"use_encryption": false,
|
||||
"verified_tx3": {},
|
||||
"wallet_type": "standard",
|
||||
"will": {},
|
||||
"winpos-qt": [
|
||||
542,
|
||||
189,
|
||||
840,
|
||||
400
|
||||
]
|
||||
}
|
||||
125
tests/wallet_8
Normal file
125
tests/wallet_8
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"active_forwardings": {},
|
||||
"addr_history": {
|
||||
"bcrt1q2rv7c2qf0z678f5kqr46gufkyc0zvjvq75lw0q": [],
|
||||
"bcrt1q3x0gthd4fkq4vuwa6sydf96ua5qhu0x577xw5q": [],
|
||||
"bcrt1q45j3lu8ykdlm0awcv9pp9gqx3d8h40acsn243l": [],
|
||||
"bcrt1q6swm30kz7p4rlzqwh0tahdev0lpr0svfmejd5y": [],
|
||||
"bcrt1q928ckf7923ekazwmmddqwe5pudkl3f5nnv7y2c": [],
|
||||
"bcrt1qa8zp4qkcf26ydyttngun9lrhu3npj6ea0q6x35": [],
|
||||
"bcrt1qaa2t5xted288p5ja6tlc56xh5rj7cy65rzs7n6": [],
|
||||
"bcrt1qamz6c8tm43t83tw2475y5kpjc7rf653ey42qcj": [],
|
||||
"bcrt1qd7s7n2260pc6zed5muk2gr4npjzcvy5xfkt6dr": [],
|
||||
"bcrt1qddr2mg23p2n4w9p947a5yxz2t47p3n3eexfu6s": [],
|
||||
"bcrt1qdmjse0qwn5n0vmh6xr4dupn5c2an83ez5645t5": [],
|
||||
"bcrt1qfgyxzkc3pk9stahk4tjxfcp0ajh33g3692yenk": [],
|
||||
"bcrt1qgcnsedcg899mp3889j2327cjzzzdq0f50dl5lu": [],
|
||||
"bcrt1qhpjnxszljgv4amgt7xgr99hwkq5u6gwzm9u00t": [],
|
||||
"bcrt1qjsgzf2dhrupywtxf6wwptfpzyst5uad4ualhkq": [],
|
||||
"bcrt1qjuvhars326f27lulzu6d77qerpgvlnnzxkgxmv": [],
|
||||
"bcrt1qk22nraj2tf0696gxc2pskgwvat689vmnr755wt": [],
|
||||
"bcrt1qkpkg9wevxwlepw80u4ugc699ewjwz4glhzjg94": [],
|
||||
"bcrt1qlnsc0umje2fxg83rcv5why3d4ydvt9cgq3dpfe": [],
|
||||
"bcrt1qmjr88850ffa6tmr5q80c4uv22yne92zut84nw2": [],
|
||||
"bcrt1qpy3nqpyjftvwd9htmsgaxrlk033ky08ala88gd": [],
|
||||
"bcrt1qq3mxpmsayg3wyjzl876hj7epusw944rwwkrmmj": [],
|
||||
"bcrt1qqm4hkq25epzyshclvfv0lmczqa6kfwsvun9qrs": [],
|
||||
"bcrt1qrfvchvw5suqavc82kwfzx7wkd5h7xl9pzlk3ly": [],
|
||||
"bcrt1qtlp7e4twkad9etqhpjtzc87aqqdks7f9jl3upg": [],
|
||||
"bcrt1qug7ypxjhaxedtl6x8gkp745fvmgn9tfepy7shy": [],
|
||||
"bcrt1qunw7t69x8hncy7f099qh6rgwedtej23hydl076": [],
|
||||
"bcrt1qx55pyw3tw3y7sqxk5j0tny6x294mkkwf69sjdw": [],
|
||||
"bcrt1qxapdcc6kdehpjzxqudm02kfe9lh4pv8ym8ecrw": [],
|
||||
"bcrt1qy8m05c7k6wstwzg4hyn95jx3csf3032r3a53tx": []
|
||||
},
|
||||
"addresses": {
|
||||
"change": [
|
||||
"bcrt1q6swm30kz7p4rlzqwh0tahdev0lpr0svfmejd5y",
|
||||
"bcrt1qa8zp4qkcf26ydyttngun9lrhu3npj6ea0q6x35",
|
||||
"bcrt1qk22nraj2tf0696gxc2pskgwvat689vmnr755wt",
|
||||
"bcrt1qunw7t69x8hncy7f099qh6rgwedtej23hydl076",
|
||||
"bcrt1qd7s7n2260pc6zed5muk2gr4npjzcvy5xfkt6dr",
|
||||
"bcrt1qddr2mg23p2n4w9p947a5yxz2t47p3n3eexfu6s",
|
||||
"bcrt1q2rv7c2qf0z678f5kqr46gufkyc0zvjvq75lw0q",
|
||||
"bcrt1qgcnsedcg899mp3889j2327cjzzzdq0f50dl5lu",
|
||||
"bcrt1qjuvhars326f27lulzu6d77qerpgvlnnzxkgxmv",
|
||||
"bcrt1qy8m05c7k6wstwzg4hyn95jx3csf3032r3a53tx"
|
||||
],
|
||||
"receiving": [
|
||||
"bcrt1qlnsc0umje2fxg83rcv5why3d4ydvt9cgq3dpfe",
|
||||
"bcrt1qjsgzf2dhrupywtxf6wwptfpzyst5uad4ualhkq",
|
||||
"bcrt1qx55pyw3tw3y7sqxk5j0tny6x294mkkwf69sjdw",
|
||||
"bcrt1qfgyxzkc3pk9stahk4tjxfcp0ajh33g3692yenk",
|
||||
"bcrt1qkpkg9wevxwlepw80u4ugc699ewjwz4glhzjg94",
|
||||
"bcrt1q3x0gthd4fkq4vuwa6sydf96ua5qhu0x577xw5q",
|
||||
"bcrt1q45j3lu8ykdlm0awcv9pp9gqx3d8h40acsn243l",
|
||||
"bcrt1qamz6c8tm43t83tw2475y5kpjc7rf653ey42qcj",
|
||||
"bcrt1qmjr88850ffa6tmr5q80c4uv22yne92zut84nw2",
|
||||
"bcrt1qxapdcc6kdehpjzxqudm02kfe9lh4pv8ym8ecrw",
|
||||
"bcrt1qtlp7e4twkad9etqhpjtzc87aqqdks7f9jl3upg",
|
||||
"bcrt1qqm4hkq25epzyshclvfv0lmczqa6kfwsvun9qrs",
|
||||
"bcrt1qaa2t5xted288p5ja6tlc56xh5rj7cy65rzs7n6",
|
||||
"bcrt1qrfvchvw5suqavc82kwfzx7wkd5h7xl9pzlk3ly",
|
||||
"bcrt1qhpjnxszljgv4amgt7xgr99hwkq5u6gwzm9u00t",
|
||||
"bcrt1qq3mxpmsayg3wyjzl876hj7epusw944rwwkrmmj",
|
||||
"bcrt1qdmjse0qwn5n0vmh6xr4dupn5c2an83ez5645t5",
|
||||
"bcrt1qug7ypxjhaxedtl6x8gkp745fvmgn9tfepy7shy",
|
||||
"bcrt1qpy3nqpyjftvwd9htmsgaxrlk033ky08ala88gd",
|
||||
"bcrt1q928ckf7923ekazwmmddqwe5pudkl3f5nnv7y2c"
|
||||
]
|
||||
},
|
||||
"channels": {},
|
||||
"db_metadata": {
|
||||
"creation_timestamp": 1786882566,
|
||||
"first_electrum_version_used": "4.8.0"
|
||||
},
|
||||
"dont_expire_htlcs": {},
|
||||
"dont_settle_htlcs": {},
|
||||
"fiat_value": {},
|
||||
"forwarding_failures": {},
|
||||
"frozen_coins": {},
|
||||
"genesis_blockhash": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
||||
"imported_channel_backups": {},
|
||||
"invoices": {},
|
||||
"keystore": {
|
||||
"derivation": "m/0h",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "2b6497af",
|
||||
"seed": "tenant sponsor drip swamp lake render coil term woman text crucial night",
|
||||
"seed_type": "segwit",
|
||||
"type": "bip32",
|
||||
"xprv": "vprv9FZ7kVJY6sZMusFainCS6LXQN38c7LypgTPWWrpUusPbzQ8yac3ew2TxWqtnig2BZaCthRQRq1EVwukggwynyCLJPCLosomTzfT79LcLw9G",
|
||||
"xpub": "vpub5UYU9zqRwF7f8ML3pojSTUU8v4y6Wohg3gK7KFE6UCvasCU889MuUpnSN8PTiu3upqtwJsEc1ewZ7J3nWa8M9rmd3N1HqdPzRRz2QmGG7D7"
|
||||
},
|
||||
"labels": {},
|
||||
"lightning_payments": {},
|
||||
"lightning_preimages": {},
|
||||
"lightning_xprv": "vprv9JtBwGkREg1THJJApfWS91ztRCgbyHETZ7Qa93Vn8WZsTx5xjZmhmHPhGgEpfWjQs1WmDXsRXLVNo1B2LizW5o6W4sgjWauV5ZmmjCrvptA",
|
||||
"notes_text": "",
|
||||
"num_parents": {},
|
||||
"onchain_channel_backups": {},
|
||||
"payment_requests": {},
|
||||
"plugin_data": {},
|
||||
"prevouts_by_scripthash": {},
|
||||
"qt-console-history": [],
|
||||
"received_mpp_htlcs": {},
|
||||
"seed_version": 71,
|
||||
"spent_outpoints": {},
|
||||
"stored_height": 2548,
|
||||
"submarine_swaps": {},
|
||||
"transactions": {},
|
||||
"tx_batches": {},
|
||||
"tx_fees": {},
|
||||
"txi": {},
|
||||
"txo": {},
|
||||
"use_encryption": false,
|
||||
"verified_tx3": {},
|
||||
"wallet_type": "standard",
|
||||
"will": {},
|
||||
"winpos-qt": [
|
||||
100,
|
||||
100,
|
||||
840,
|
||||
400
|
||||
]
|
||||
}
|
||||
111
tests/wallet_9
Normal file
111
tests/wallet_9
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"addr_history": {
|
||||
"bcrt1q0x4y26e8xzwtjc209a7nhat52x4tv0jumevm4r": [],
|
||||
"bcrt1q23g4fc35xue05lunzq4n02h55wdw9ym89djtcg": [],
|
||||
"bcrt1q4fc4wlhckn0hvawwr66jf87vj40cg3ysgpw9z2": [],
|
||||
"bcrt1q4rg6j2zs7kgfwwfkarzkdh995ugp52j0g08upc": [],
|
||||
"bcrt1q4u0z2ydtjuucy8vz6ln27ksjmgv2aem3ta2xsh": [],
|
||||
"bcrt1q5em594x47uvn8c62pr5dld60s0jg6at9kuuwnl": [],
|
||||
"bcrt1q9n8fhhh5y7k4x5rwstu84zymd85sd05ksw2aww": [],
|
||||
"bcrt1qad7y4zj060z4xqk6nyd47majuyfsq8f0dqc34s": [],
|
||||
"bcrt1qcvfgvme7q36w52qu94vqk29dn4xfxdkvcwuhg4": [],
|
||||
"bcrt1qdk0um5qeuh39v80zrdwssngnudfnfs4yqztkzl": [],
|
||||
"bcrt1qduzwfg0nrmsnax60r29ema0cfhn08ehz3adhxz": [],
|
||||
"bcrt1qf8763en08t5man2xnv8ndkjstkljtatgnnvdkm": [],
|
||||
"bcrt1qfuwp7f8z4uqrfh5jsff5p9cefnfded8w9p37g0": [],
|
||||
"bcrt1qglhv5uskfhnrnr09r7gmgt68hcch67xzax7lea": [],
|
||||
"bcrt1qgmtzgutnvkjtyu2eywpy9t2ma75w8gxt4gf3zy": [],
|
||||
"bcrt1qgzv7cra6rp5ukeqy8hragdlrp4dmecedeuhvez": [],
|
||||
"bcrt1qh9w20rnkhputhqp0wat6rktqqhlf3eqwh9cd07": [],
|
||||
"bcrt1qj5tqawfkp79wu7p9rq9x9xzpngqxxfxhw6md0s": [],
|
||||
"bcrt1qkwqk2mcvnka24jpyt0lhjhr0w6yq64mxsepgs2": [],
|
||||
"bcrt1qky6mzgkfglascuk0azcaar65x6hyw4ckn3nynd": [],
|
||||
"bcrt1qr0lwmu79urkmmexlh3hm8gy65eseeuye9q8hhl": [],
|
||||
"bcrt1qr8agcgek0tn0gza42d0ym5lhgwlwfhtypdee73": [],
|
||||
"bcrt1qrp9kdgxy8p44sgfjwj46kcg22jq2fhddmwlqwj": [],
|
||||
"bcrt1qscvf4xcg6e209j3fe5ptmmexwlm7sgjmguls5x": [],
|
||||
"bcrt1qt8rftzkhyr30xkdhxzwtfz2n94gz95cy5njv2g": [],
|
||||
"bcrt1qu6hehtcx4yur6gqqgp5xx2zehujyzj0qhdmj7m": [],
|
||||
"bcrt1qvxte82z2kgajjupzcqg94jj4sp03xqst9phf8m": [],
|
||||
"bcrt1qx87xuvhm8kcz2qpjpc4zg3j0cztzsr0sns20pp": [],
|
||||
"bcrt1qxatsqu00j9gx88kaafd5je0lu9atf48w5ftcgn": [],
|
||||
"bcrt1qxlkwctc5vpj0vqgma4c28r6y9mr8thd84htp98": []
|
||||
},
|
||||
"addresses": {
|
||||
"change": [
|
||||
"bcrt1q0x4y26e8xzwtjc209a7nhat52x4tv0jumevm4r",
|
||||
"bcrt1qvxte82z2kgajjupzcqg94jj4sp03xqst9phf8m",
|
||||
"bcrt1qj5tqawfkp79wu7p9rq9x9xzpngqxxfxhw6md0s",
|
||||
"bcrt1q4fc4wlhckn0hvawwr66jf87vj40cg3ysgpw9z2",
|
||||
"bcrt1qscvf4xcg6e209j3fe5ptmmexwlm7sgjmguls5x",
|
||||
"bcrt1qdk0um5qeuh39v80zrdwssngnudfnfs4yqztkzl",
|
||||
"bcrt1qxatsqu00j9gx88kaafd5je0lu9atf48w5ftcgn",
|
||||
"bcrt1qfuwp7f8z4uqrfh5jsff5p9cefnfded8w9p37g0",
|
||||
"bcrt1qduzwfg0nrmsnax60r29ema0cfhn08ehz3adhxz",
|
||||
"bcrt1qr0lwmu79urkmmexlh3hm8gy65eseeuye9q8hhl"
|
||||
],
|
||||
"receiving": [
|
||||
"bcrt1qr8agcgek0tn0gza42d0ym5lhgwlwfhtypdee73",
|
||||
"bcrt1qgzv7cra6rp5ukeqy8hragdlrp4dmecedeuhvez",
|
||||
"bcrt1qky6mzgkfglascuk0azcaar65x6hyw4ckn3nynd",
|
||||
"bcrt1qu6hehtcx4yur6gqqgp5xx2zehujyzj0qhdmj7m",
|
||||
"bcrt1qrp9kdgxy8p44sgfjwj46kcg22jq2fhddmwlqwj",
|
||||
"bcrt1q23g4fc35xue05lunzq4n02h55wdw9ym89djtcg",
|
||||
"bcrt1qxlkwctc5vpj0vqgma4c28r6y9mr8thd84htp98",
|
||||
"bcrt1q4u0z2ydtjuucy8vz6ln27ksjmgv2aem3ta2xsh",
|
||||
"bcrt1q4rg6j2zs7kgfwwfkarzkdh995ugp52j0g08upc",
|
||||
"bcrt1q5em594x47uvn8c62pr5dld60s0jg6at9kuuwnl",
|
||||
"bcrt1qgmtzgutnvkjtyu2eywpy9t2ma75w8gxt4gf3zy",
|
||||
"bcrt1qf8763en08t5man2xnv8ndkjstkljtatgnnvdkm",
|
||||
"bcrt1qglhv5uskfhnrnr09r7gmgt68hcch67xzax7lea",
|
||||
"bcrt1qcvfgvme7q36w52qu94vqk29dn4xfxdkvcwuhg4",
|
||||
"bcrt1qad7y4zj060z4xqk6nyd47majuyfsq8f0dqc34s",
|
||||
"bcrt1qkwqk2mcvnka24jpyt0lhjhr0w6yq64mxsepgs2",
|
||||
"bcrt1qt8rftzkhyr30xkdhxzwtfz2n94gz95cy5njv2g",
|
||||
"bcrt1qh9w20rnkhputhqp0wat6rktqqhlf3eqwh9cd07",
|
||||
"bcrt1q9n8fhhh5y7k4x5rwstu84zymd85sd05ksw2aww",
|
||||
"bcrt1qx87xuvhm8kcz2qpjpc4zg3j0cztzsr0sns20pp"
|
||||
]
|
||||
},
|
||||
"db_metadata": {
|
||||
"creation_timestamp": 1786883660,
|
||||
"first_electrum_version_used": "4.8.0"
|
||||
},
|
||||
"fiat_value": {},
|
||||
"frozen_coins": {},
|
||||
"genesis_blockhash": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
||||
"invoices": {},
|
||||
"keystore": {
|
||||
"derivation": "m/84h/0h/0h",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "62f91f1a",
|
||||
"type": "bip32",
|
||||
"xprv": "vprv9Lk9ZvPm2qJwCUH2NPtjrseppkqh1SJzazUMWoqBiV6nCswHgHThHVbU6TNE9xY7rN2F5e9krR7PC21fcMKfy1x1158eNJ3iGyCQXinRFwW",
|
||||
"xpub": "vpub5ZjVyRvesCsEQxMVURRkE1bZNngBQu2qxDPxKCEoGpdm5gGSDpmwqHuwwi3BvxVSTSkBdRA7zNeFQaAi9DG3y2TLefEteRyjF8tLqF9TJDW"
|
||||
},
|
||||
"labels": {},
|
||||
"notes_text": "",
|
||||
"num_parents": {},
|
||||
"payment_requests": {},
|
||||
"plugin_data": {},
|
||||
"prevouts_by_scripthash": {},
|
||||
"qt-console-history": [],
|
||||
"seed_version": 71,
|
||||
"spent_outpoints": {},
|
||||
"stored_height": 2552,
|
||||
"transactions": {},
|
||||
"tx_batches": {},
|
||||
"tx_fees": {},
|
||||
"txi": {},
|
||||
"txo": {},
|
||||
"use_encryption": false,
|
||||
"verified_tx3": {},
|
||||
"wallet_type": "standard",
|
||||
"will": {},
|
||||
"winpos-qt": [
|
||||
100,
|
||||
100,
|
||||
840,
|
||||
400
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user