diff --git a/bal/core/checkalive.py b/bal/core/checkalive.py new file mode 100644 index 0000000..7081c40 --- /dev/null +++ b/bal/core/checkalive.py @@ -0,0 +1,71 @@ +""" +bal.core.checkalive +=================== + +The "Check Alive" policy: the single reference timestamp (``date_to_check``) +against which every will-validity check is evaluated, and the BASIC/ADVANCED +mode rules that decide it. + +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 typing import Any + +from .plugin_base import BalTimestamp + + +class CheckAliveError(Exception): + """Raised when the "check alive" date is in the past.""" + + def __init__(self, timestamp_to_check): + self.timestamp_to_check = timestamp_to_check + + def __str__(self): + return "Check alive expired please update it: {}".format( + datetime.fromtimestamp(self.timestamp_to_check).isoformat() + ) + + +def resolve_date_to_check( + is_basic_mode: bool, will_settings: Any, now: float | None = None +) -> float: + """Return the reference timestamp for every will-validity check. + + ``date_to_check`` is the single reference timestamp that EVERY downstream + check reads: the build filter, the heir count, ``check_will_expired``, + ``check_amounts`` and the locktime-vs-threshold guard. + + * BASIC mode: the Check Alive is hidden and NOT editable, so it must never + govern those checks. ``date_to_check`` is set to *now*: every check is + evaluated against the current moment (the Check Alive effectively does not + exist) while the delivery locktime is still fully enforced. + * ADVANCED mode: the user-controlled stored threshold is used as-is. + + Args: + is_basic_mode: ``True`` for the SIMPLE / BASIC user type. + will_settings: the per-wallet settings dict (``"threshold"`` key). + now: overridable clock for tests; defaults to ``datetime.now()``. + + Returns: + The reference timestamp (float, UNIX seconds). + """ + if is_basic_mode: + return (now if now is not None else datetime.now().timestamp()) + return BalTimestamp(will_settings["threshold"]).to_timestamp() + + +def check_alive_expired( + is_basic_mode: bool, date_to_check: float, now: float | None = None +) -> bool: + """True when the Check Alive guard should fire (``CheckAliveError``). + + Only ADVANCED mode can be "expired": in BASIC mode the Check Alive is inert + by construction, so a passed check-alive date must never force a postpone or + rewrite of the will. + """ + if is_basic_mode: + return False + current = now if now is not None else datetime.now().timestamp() + return date_to_check < current diff --git a/bal/core/heirs.py b/bal/core/heirs.py index 31df416..7a169a5 100644 --- a/bal/core/heirs.py +++ b/bal/core/heirs.py @@ -630,7 +630,19 @@ class Heirs(dict, Logger): def buildTransactions( self, bal_plugin, wallet, tx_fees=None, utxos=None, from_locktime=0 ): - Heirs._validate(self) + _before = list(self.keys()) + Heirs._validate(self, persist=False) + _removed = [k for k in _before if k not in self] + if _removed: + # The build skips invalid heirs, but they are only dropped in memory + # (persist=False): the wallet still keeps them, so the user can fix + # or remove them deliberately instead of losing them silently. + _logger.warning( + "buildTransactions: skipped %d invalid heir(s) (kept in wallet, " + "not removed): %s", + len(_removed), + ", ".join(_removed), + ) if len(self) <= 0: _logger.info("while building transactions there was no heirs") return @@ -877,16 +889,22 @@ class Heirs(dict, Logger): return (address, amount, locktime) @staticmethod - def _validate(data, timestamp_to_check=False): + def _validate(data, timestamp_to_check=False, persist=True): for k, v in list(data.items()): if k == "heirs": - return Heirs._validate(v, timestamp_to_check) + return Heirs._validate(v, timestamp_to_check, persist) try: Heirs.validate_heir(k, v, timestamp_to_check) except Exception as e: _logger.info(f"exception heir removed {e}") - data.pop(k) + if persist: + data.pop(k) + else: + # Drop the invalid heir in memory only, so the overridden + # Heirs.pop (which calls save()) is not triggered: a build + # must not silently delete heirs from the wallet. + dict.pop(data, k) return data diff --git a/bal/core/input_rules.py b/bal/core/input_rules.py new file mode 100644 index 0000000..c75efbe --- /dev/null +++ b/bal/core/input_rules.py @@ -0,0 +1,193 @@ +""" +bal.core.input_rules +==================== + +Pure, GUI-free rules for the plugin's text-input widgets: locktime bounds and +acceptance, the RAW locktime sanitisation ("30d"/"1y"), and the percentage-or- +amount field normalisation. + +These used to live inside the Qt widget classes in ``bal.gui.qt.widgets``. +Keeping them here makes them testable without Qt and lets any GUI front-end +reuse the exact same parsing rules. +""" + +from datetime import datetime +from decimal import Decimal +from typing import Any, Optional, Tuple, Union + +from electrum.bitcoin import NLOCKTIME_BLOCKHEIGHT_MAX, NLOCKTIME_MAX, NLOCKTIME_MIN + +__all__ = [ + "NLOCKTIME_BLOCKHEIGHT_MAX", + "NLOCKTIME_MAX", + "NLOCKTIME_MIN", + "LockTimeEditor", + "normalize_locktime_raw_text", + "normalize_perc_amount_text", + "parse_perc_amount", + "replace_dy_suffixes", +] + + +class LockTimeEditor: + """Acceptance bounds shared by the RAW and Date locktime editors. + + A Qt widget mixin historically; the pure parts (bounds and acceptance test) + live here so they can be tested and reused without Qt. Widget subclasses + override ``min_allowed_value``/``max_allowed_value`` to tighten the bounds. + """ + + min_allowed_value = NLOCKTIME_MIN + max_allowed_value = NLOCKTIME_MAX + alarm = None + + def get_value(self) -> Optional[int]: + raise NotImplementedError() + + def set_value(self, x: Any, force=True) -> None: + raise NotImplementedError() + + @classmethod + def is_acceptable_locktime(cls, x: Any) -> bool: + """True when ``x`` (string or int) is within the allowed locktime bounds. + + An empty/falsy value is accepted (the field is not yet filled in). + """ + if not x: # e.g. empty string + return True + try: + x = int(x) + except Exception as _e: + return False + return cls.min_allowed_value <= x <= cls.max_allowed_value + + @staticmethod + def get_max_allowed_timestamp() -> int: + """Highest locktime timestamp accepted on this platform. + + On 32-bit ``time_t`` Windows builds ``datetime.fromtimestamp`` overflows + past 2038, so the ceiling is clamped to INT32_MAX (see #6170). + """ + ts = NLOCKTIME_MAX + # Test if this value is within the valid timestamp limits (which is + # platform-dependent). see #6170 + try: + datetime.fromtimestamp(ts) + except (OSError, OverflowError): + ts = 2**31 - 1 # INT32_MAX + datetime.fromtimestamp(ts) # test if raises + return ts + + +def replace_dy_suffixes(text: str) -> str: + """Strip the relative-time suffixes (d/y) from ``text``. + + Only days ("d") and years ("y") are supported. The block-height suffix + ("b") was removed (A1): locktimes are always timestamps now. + """ + return str(text).replace("d", "").replace("y", "") + + +def _checkbdy(s: str, pos: int, appendix: str) -> Tuple[int, str]: + """Keep a ``d``/``y`` suffix typed right after an existing suffix. + + When the character just before ``pos`` equals ``appendix``, the text is + re-normalised so only one suffix remains. + """ + try: + charpos = pos - 1 + charpos = max(0, charpos) + charpos = min(len(s) - 1, charpos) + if appendix == s[charpos]: + s = replace_dy_suffixes(s) + appendix + pos = charpos + except Exception: + pass + return pos, s + + +def normalize_locktime_raw_text( + text: str, pos: int +) -> Tuple[str, bool, bool, int]: + """Sanitise the RAW locktime field text. + + Only digits plus the day ("d") and year ("y") suffixes are kept; the block + suffix ("b") is removed (A1). Exactly one ``d``/``y`` suffix survives. + + Args: + text: the raw field text. + pos: the cursor position within ``text``. + + Returns: + ``(clean, isdays, isyears, new_pos)`` where ``clean`` is the sanitised + text and ``new_pos`` the adjusted cursor position. + """ + text = text.strip() + chars = "0123456789dy" + pos = len("".join([i for i in text[:pos] if i in chars])) + s = "".join([i for i in text if i in chars]) + isdays = False + isyears = False + + pos, s = _checkbdy(s, pos, "d") + pos, s = _checkbdy(s, pos, "y") + + if "d" in s: + isdays = True + if "y" in s: + isyears = True + + if isdays: + s = replace_dy_suffixes(s) + "d" + if isyears: + s = replace_dy_suffixes(s) + "y" + return s, isdays, isyears, pos + + +def normalize_perc_amount_text(text: str, decimal_point: str) -> Tuple[str, bool]: + """Sanitise the amount-or-percentage field text. + + Keeps digits, ``%`` and the decimal point; a trailing ``%`` marks the value + as a percentage (``is_perc``). At most 8 decimal digits after the point. + + Args: + text: the raw field text. + decimal_point: the decimal separator character (``electrum`` uses + ``DECIMAL_POINT``, locale dependent). + + Returns: + ``(clean, is_perc)``. + """ + text = text.strip() + chars = "0123456789%" + chars += decimal_point + + s = "".join([i for i in text if i in chars]) + + if "%" in s: + is_perc = True + s = s.replace("%", "") + else: + is_perc = False + + if decimal_point in s: + p = s.find(decimal_point) + s = s.replace(decimal_point, "") + s = s[:p] + decimal_point + s[p : p + 8] + if is_perc: + s += "%" + + return s, is_perc + + +def parse_perc_amount(text: str, decimal_point: str) -> Union[None, Decimal, int]: + """Parse an amount-or-percentage field text into a numeric value. + + Returns ``None`` when the text cannot be parsed. + """ + try: + text = text.replace(decimal_point, ".") + text = text.replace("%", "") + return Decimal(text) + except Exception: + return None diff --git a/bal/core/plugin_base.py b/bal/core/plugin_base.py index 8ade25b..fc600d0 100644 --- a/bal/core/plugin_base.py +++ b/bal/core/plugin_base.py @@ -306,7 +306,10 @@ class BalPlugin(BasePlugin): config, "bal_event_summary", "BAL -Will execution of $wallet_name" ) - # Default will-executor servers, keyed by network. + # Default will-executor servers, keyed by network. These addresses are + # the ones currently reported by each server's /info endpoint and + # are refreshed again on ping; testnet/testnet4 must NOT be regtest + # (bcrt1...) addresses, which are invalid on those networks. self.WILLEXECUTORS = BalConfig( config, "bal_willexecutors", @@ -325,7 +328,7 @@ class BalPlugin(BasePlugin): "base_fee": 100000, "status": "New", "info": "Bitcoin After Life Will Executor", - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", + "address": "tb1qp5tmrvtm6dmz23mzkf55n5d53xh39wt0gwpp5m", "selected": True, } }, @@ -334,7 +337,7 @@ class BalPlugin(BasePlugin): "base_fee": 100000, "status": "New", "info": "Bitcoin After Life Will Executor", - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", + "address": "tb1qfj5ewmczg8ck2z0eeff6uysdrxd4qdy2ltrx5a", "selected": True, } }, diff --git a/bal/core/reminders.py b/bal/core/reminders.py new file mode 100644 index 0000000..9e8e677 --- /dev/null +++ b/bal/core/reminders.py @@ -0,0 +1,228 @@ +""" +bal.core.reminders +================== + +Pure, GUI-free logic for the dead-man's-switch calendar reminders: choosing the +reminder offsets (BASIC vs ADVANCED modes) and rendering them as an RFC-5545 +iCalendar (.ics) document. + +Everything in this module is stdlib-only, so it can be imported and tested +without Electrum or Qt (e.g. in the lint venv). +""" + +import os +import tempfile +from datetime import datetime, timedelta, timezone +from typing import Optional + + +def compute_reminder_offsets(days, count): + """Return the reminder offsets (in days BEFORE the deadline) for an .ics event. + + Group D / D1. The reminders are spread uniformly across the check-alive + period and always fall *before* the delivery deadline, i.e. every returned + offset is ``>= 1`` (a reminder exactly on the deadline would be useless). + + Rules: + * ``count`` is the requested number of reminders (the settings dialog + caps it at 5, default 3). + * at most ONE reminder per available day: the effective number is + ``min(count, days)``; + * with ``days`` available days, offsets are chosen as evenly spaced + points inside ``[1, days]`` (1 = the day before the deadline, ``days`` + = the first day of the period), de-duplicated and returned sorted + descending (earliest reminder first). + + Args: + days: number of whole days between check-alive and the deadline. + count: requested number of reminders. + + Returns: + A list of integer day-offsets (each ``>= 1``), e.g. ``[22, 15, 8]`` for + ``days=30, count=3``. Empty if there is no room for any reminder. + """ + # No room for any reminder (deadline today or already passed). + if days < 1 or count < 1: + return [] + + # Never more reminders than available days (one per day at most). + effective = min(int(count), int(days)) + + # A single reminder: put it one day before the deadline. + if effective == 1: + return [1] + + # Spread "effective" points evenly inside [1, days]. Using i/(effective-1) + # for i in 0..effective-1 gives fractions 0..1; map them onto [1, days]. + # This places the first reminder at the start of the period (offset ~days) + # and the last one one day before the deadline (offset 1). + offsets = set() + for i in range(effective): + frac = i / (effective - 1) # 0.0 .. 1.0 + # offset = days at frac 0 (start), 1 at frac 1 (just before deadline). + offset = round(days - frac * (days - 1)) + offset = max(1, min(days, offset)) + offsets.add(offset) + + # Sorted descending: earliest reminder (largest offset) first. + return sorted(offsets, reverse=True) + + +# Fixed reminder offsets (in days BEFORE the delivery date) used in BASIC mode. +# In BASIC the check-alive parameter is hidden/unmanaged, so reminders cannot be +# spread over it; instead the owner asked for three fixed reminders: 30, 10 and +# 1 day before the inheritance delivery date. +BASIC_REMINDER_OFFSETS = (30, 10, 1) + + +def basic_reminder_offsets(days_to_deadline): + """Return the BASIC-mode reminder offsets that still fall in the future. + + BASIC mode uses the fixed offsets in ``BASIC_REMINDER_OFFSETS`` (30, 10 and + 1 day before the delivery date). Any offset that would land in the past is + dropped, because a reminder before "today" is useless: if the delivery date + is only ``days_to_deadline`` days away, only the offsets that are ``<= + days_to_deadline`` are kept. + + Args: + days_to_deadline: whole days from now until the delivery date. + + Returns: + A list of integer day-offsets (each ``>= 1``), sorted as in + ``BASIC_REMINDER_OFFSETS`` (descending: earliest reminder first). Empty + when the delivery date is less than one day away. + """ + horizon = max(int(days_to_deadline), 0) + return [off for off in BASIC_REMINDER_OFFSETS if 1 <= off <= horizon] + + +def format_time(time) -> str: + """Render a datetime as an RFC-5545 UTC timestamp (``YYYYMMDDTHHMMSSZ``).""" + return time.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + + +def fold_ical_line(line: str, limit: int = 75) -> str: + """Fold a line to at most ``limit`` bytes per RFC-5545, without splitting + multi-byte UTF-8 characters. Continuation lines start with a space.""" + encoded = line.encode("utf-8") + parts = [] + while len(encoded) > limit: + # cut without splitting a UTF-8 continuation byte + cut = limit + while (encoded[cut] & 0xC0) == 0x80: # byte de continuazione UTF-8 + cut -= 1 + parts.append(encoded[:cut].decode("utf-8")) + encoded = encoded[cut:] + parts.append(encoded.decode("utf-8")) + return "\r\n ".join(parts) + + +def ical_escape(text: str) -> str: + """Escape a string per RFC-5545: backslash, semicolon, comma, newlines.""" + text = ( + text.replace("\\", "\\\\") + .replace(";", "\\;") + .replace(",", "\\,") + ) + return "\r\n".join(fold_ical_line(line) for line in text.split("\r\n")) + + +def write_temp_ics(content: str) -> str: + """Write ``content`` to a temporary ``.ics`` file and return its path.""" + fd, path = tempfile.mkstemp(prefix="event_", suffix=".ics") + with os.fdopen(fd, "wb") as f: + f.write(content.encode("utf-8")) + return path + + +def build_ics_reminders( + *, + locktime: datetime, + basic_mode: bool, + description: str, + summary: str, + wallet_name: str, + heirs_details: str, + version: str, + num_reminders: int = 3, + now: Optional[datetime] = None, + threshold: Optional[datetime] = None, +) -> Optional[str]: + """Build the ``.ics`` content with one VEVENT per reminder date. + + Group D / D1 (revised): N *separate* VEVENTs, one per reminder date, so the + user sees several distinct appointments in their calendar. The reminder + offsets come from :func:`basic_reminder_offsets` (BASIC mode) or + :func:`compute_reminder_offsets` (ADVANCED mode, spread over the check-alive + period). + + Args: + locktime: the delivery deadline (datetime). + basic_mode: use the fixed BASIC offsets instead of spreading over the + check-alive period. + description: raw EVENT_DESCRIPTION template; ``$wallet_name`` and + ``$heirs_complete`` placeholders are substituted and escaped. + summary: raw EVENT_SUMMARY template; ``$wallet_name`` is substituted. + wallet_name: label used in the UID and template substitutions. + heirs_details: pre-formatted heir list injected into ``description``. + version: plugin version, embedded in the PRODID line. + num_reminders: requested reminder count (ADVANCED mode only). + now: "today" reference; defaults to ``datetime.now()``. + threshold: check-alive date (ADVANCED mode only; required there). + + Returns: + The ``.ics`` content string, or ``None`` when no reminder falls in the + future (the delivery date is too close or already passed) so the caller + can show a warning instead of producing an empty-looking file. + """ + now = now if now is not None else datetime.now() + + if basic_mode: + days_to_deadline = (locktime - now).days + offsets = basic_reminder_offsets(days_to_deadline) + else: + if threshold is None: + raise ValueError("threshold is required in ADVANCED mode") + days = (locktime - threshold).days + offsets = compute_reminder_offsets(days, num_reminders) + + # ToDo #2: no future reminder means there are no events to write. Return + # None so the caller shows a clear warning instead of an empty .ics file. + if not offsets: + return None + + event_description = ical_escape( + f"{description}" + .replace("$wallet_name", str(wallet_name)) + .replace("$heirs_complete", heirs_details) + ) + summary_base = f"{summary}".replace("$wallet_name", str(wallet_name)) + + lines = [ + "BEGIN:VCALENDAR", + "VERSION:2.0", + f"PRODID:-//Bitcoin After Life//Electrum Plugin/{version}", + ] + + # One separate VEVENT per reminder offset (its own date in the calendar). + total = len(offsets) + for idx, offset in enumerate(offsets, start=1): + # The visible date of this event: "offset" days before the deadline. + event_dt = format_time(locktime - timedelta(days=offset)) + # Suffix the summary so the N events are easy to tell apart. + event_summary = ical_escape(f"{summary_base} (reminder {idx}/{total})") + lines.extend([ + "BEGIN:VEVENT", + # Offset in the UID keeps each event unique (no merging). + f"UID:bal-{str(wallet_name)}-{offset}d", + f"DTSTAMP:{format_time(now)}", + f"DTSTART:{event_dt}", + f"DTEND:{event_dt}", + f"SUMMARY:{event_summary}", + f"DESCRIPTION:{event_description}", + "END:VEVENT", + ]) + + lines.append("END:VCALENDAR") + lines = [s.rstrip("\r\n") for s in lines] + return "\r\n".join(lines) + "\r\n" diff --git a/bal/gui/qt/calendar.py b/bal/gui/qt/calendar.py index e6552a6..ec4979d 100644 --- a/bal/gui/qt/calendar.py +++ b/bal/gui/qt/calendar.py @@ -7,11 +7,16 @@ iCalendar (.ics) generation and "open with default calendar app" helper. When a will is built, the plugin can create a calendar event reminding the user to "check in" before the locktime expires. This module turns the event data into an RFC-5545 .ics file and opens it with the OS default application. + +The pure RFC-5545 logic (offsets, escaping, folding, the unified .ics builder, +``write_temp_ics``) lives in :mod:`bal.core.reminders`; this module keeps only +the Qt button and the OS/subprocess glue. """ from PyQt6.QtGui import QAction from PyQt6.QtWidgets import QToolButton +from ...core.reminders import write_temp_ics from .common import * from .common import _, _logger # underscore names are not re-exported by "import *" @@ -57,7 +62,7 @@ class BalCalendarButton(QToolButton): try: content = self._ics_provider() if content: - self._calendar_temp_path = BalCalendar.write_temp_ics(content) + self._calendar_temp_path = write_temp_ics(content) else: self._calendar_temp_path = None self._bal_window.show_warning( @@ -149,13 +154,6 @@ class BalCalendarButton(QToolButton): class BalCalendar: - @staticmethod - def write_temp_ics(content): - fd, path = tempfile.mkstemp(prefix="event_", suffix=".ics") - with os.fdopen(fd, "wb") as f: - f.write(content.encode("utf-8")) - return path - @staticmethod def open_with_default_app(calendar_app, path): _logger.debug("opening calendar app") @@ -184,37 +182,3 @@ class BalCalendar: if os.path.isdir(desktop): return desktop return home - - - @staticmethod - def format_time(time): - return time.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ") - #return time.astimezone(timezone.utc).strftime("%Y%m%d") - - @staticmethod - def ical_escape(text: str) -> str: - # escape per RFC5545: backslash, ; , newlines - text = ( - text.replace("\\", "\\\\") - .replace(";", "\\;") - .replace(",", "\\,") - ) - return "\r\n".join( - BalCalendar.fold_ical_line(line) - for line in text.split("\r\n") - ) - - @staticmethod - def fold_ical_line(line: str, limit: int = 75) -> str: - # ritorna linee separate da CRLF e folding con spazio iniziale sulle righe successive - encoded = line.encode("utf-8") - parts = [] - while len(encoded) > limit: - # taglia senza spezzare byte UTF-8 - cut = limit - while (encoded[cut] & 0xC0) == 0x80: # byte di continuazione UTF-8 - cut -= 1 - parts.append(encoded[:cut].decode("utf-8")) - encoded = encoded[cut:] - parts.append(encoded.decode("utf-8")) - return "\r\n ".join(parts) diff --git a/bal/gui/qt/common.py b/bal/gui/qt/common.py index dd6cf38..3ce13ca 100644 --- a/bal/gui/qt/common.py +++ b/bal/gui/qt/common.py @@ -12,7 +12,7 @@ hosts a few GUI helpers that do not deserve a module of their own: * :func:`add_widget` - add a labelled widget (plus optional help) to a grid. * :func:`log_error` - format an exception traceback for a dialog. * :func:`export_meta_gui` - export plugin metadata to a JSON file. - * :class:`CheckAliveError`- raised when the "check alive" date is in the past. + (:class:`CheckAliveError` now lives in ``bal.core.checkalive``.) """ import copy @@ -181,18 +181,6 @@ def add_widget(grid, label, widget, row, help_): -class CheckAliveError(Exception): - def __init__(self, timestamp_to_check): - self.timestamp_to_check = timestamp_to_check - - def __str__(self): - return "Check alive expired please update it: {}".format( - datetime.fromtimestamp(self.timestamp_to_check).isoformat() - ) - - - - def log_error(exec_info, window=None): """Log an error and optionally show it. diff --git a/bal/gui/qt/dialogs.py b/bal/gui/qt/dialogs.py index ad6d165..eb613cc 100644 --- a/bal/gui/qt/dialogs.py +++ b/bal/gui/qt/dialogs.py @@ -19,14 +19,14 @@ use them (see ``lists`` imports below). from typing import TYPE_CHECKING -from .calendar import BalCalendar, BalCalendarButton +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 .widgets import ( WillSettingsWidget, WillWidget, - basic_reminder_offsets, - compute_reminder_offsets, ) if TYPE_CHECKING: @@ -1598,7 +1598,7 @@ class BalBuildWillDialog(BalDialog): def _ics_provider(self): """Return the .ics content for the current will data.""" - from datetime import datetime, timedelta + from datetime import datetime try: locktime_ts = Util.parse_locktime_string( @@ -1606,22 +1606,25 @@ class BalBuildWillDialog(BalDialog): ) locktime = datetime.fromtimestamp(locktime_ts) - if self.bal_window.bal_plugin.is_basic_mode(): - days_to_deadline = (locktime - datetime.now()).days - offsets = basic_reminder_offsets(days_to_deadline) + basic_mode = self.bal_window.bal_plugin.is_basic_mode() + if basic_mode: + raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.default + raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.default + threshold = None + num_reminders = 3 else: threshold_ts = BalTimestamp( self.bal_window.will_settings["threshold"] ).to_timestamp() threshold = datetime.fromtimestamp(threshold_ts) - days = (locktime - threshold).days + raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.get() + raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.get() try: - count = int(self.bal_window.bal_plugin.NUM_REMINDERS.get()) + num_reminders = int( + self.bal_window.bal_plugin.NUM_REMINDERS.get() + ) except Exception: - count = 3 - offsets = compute_reminder_offsets(days, count) - - now = BalCalendar.format_time(datetime.now()) + num_reminders = 3 heirs_details = "\r\n".join( f" {heir} - {self.bal_window.heirs[heir][0]}, " @@ -1629,56 +1632,21 @@ class BalBuildWillDialog(BalDialog): for heir in self.bal_window.heirs ) - if self.bal_window.bal_plugin.is_basic_mode(): - raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.default - raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.default - else: - raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.get() - raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.get() - - event_description = BalCalendar.ical_escape( - f"{raw_description}" - .replace("$wallet_name", str(self.bal_window.wallet)) - .replace("$heirs_complete", heirs_details) + # ToDo #2: when no reminder falls in the future (the delivery date is + # too close or already passed), build_ics_reminders returns None so + # the caller shows a clear warning instead of producing an empty, + # seemingly-broken .ics file. + return build_ics_reminders( + locktime=locktime, + basic_mode=basic_mode, + description=raw_description, + summary=raw_summary, + wallet_name=str(self.bal_window.wallet), + heirs_details=heirs_details, + version=self.bal_window.bal_plugin.version, + num_reminders=num_reminders, + threshold=threshold, ) - summary_base = ( - f"{raw_summary}" - .replace("$wallet_name", str(self.bal_window.wallet)) - ) - - lines = [ - "BEGIN:VCALENDAR", - "VERSION:2.0", - "PRODID:-//Bitcoin After Life//Electrum Plugin/" - f"{self.bal_window.bal_plugin.version}", - ] - - total = len(offsets) - # ToDo #2: if no reminder falls in the future (the delivery date is - # too close or already passed), there are no events to write. - # Return None so the caller shows a clear warning instead of - # producing an empty, seemingly-broken .ics file. - if total == 0: - return None - for idx, offset in enumerate(offsets, start=1): - event_dt = BalCalendar.format_time(locktime - timedelta(days=offset)) - summary = BalCalendar.ical_escape( - f"{summary_base} (reminder {idx}/{total})" - ) - lines.extend([ - "BEGIN:VEVENT", - f"UID:bal-{str(self.bal_window.wallet)}-{offset}d", - f"DTSTAMP:{now}", - f"DTSTART:{event_dt}", - f"DTEND:{event_dt}", - f"SUMMARY:{summary}", - f"DESCRIPTION:{event_description}", - "END:VEVENT", - ]) - - lines.append("END:VCALENDAR") - lines = [s.rstrip("\r\n") for s in lines] - return "\r\n".join(lines) + "\r\n" except Exception as e: _logger.error(f"failed to generate .ics: {e}") return None @@ -1706,7 +1674,12 @@ class BalBuildWillDialog(BalDialog): try: if txs := self.bal_window.sign_transactions(password): for txid, tx in txs.items(): - self.bal_window.willitems[txid].tx = copy.deepcopy(tx) + # Re-parse instead of deepcopy (the signed tx can carry + # wallet-derived input info holding a threading.RLock, + # which copy.deepcopy cannot pickle). + self.bal_window.willitems[txid].tx = Will.get_tx_from_any( + str(tx) + ) self.bal_window.save_willitems() self.msg_set_signing(self.msg_ok()) except Exception as e: diff --git a/bal/gui/qt/widgets.py b/bal/gui/qt/widgets.py index 5abf45d..0a2a477 100644 --- a/bal/gui/qt/widgets.py +++ b/bal/gui/qt/widgets.py @@ -20,6 +20,13 @@ Contents: from typing import TYPE_CHECKING +from ...core.input_rules import ( + LockTimeEditor, + normalize_locktime_raw_text, + normalize_perc_amount_text, + parse_perc_amount, +) +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 *" @@ -28,86 +35,6 @@ if TYPE_CHECKING: from .window import BalWindow -def compute_reminder_offsets(days, count): - """Return the reminder offsets (in days BEFORE the deadline) for an .ics event. - - Group D / D1. The reminders are spread uniformly across the check-alive - period and always fall *before* the delivery deadline, i.e. every returned - offset is ``>= 1`` (a reminder exactly on the deadline would be useless). - - Rules: - * ``count`` is the requested number of reminders (the settings dialog - caps it at 5, default 3). - * at most ONE reminder per available day: the effective number is - ``min(count, days)``; - * with ``days`` available days, offsets are chosen as evenly spaced - points inside ``[1, days]`` (1 = the day before the deadline, ``days`` - = the first day of the period), de-duplicated and returned sorted - descending (earliest reminder first). - - Args: - days: number of whole days between check-alive and the deadline. - count: requested number of reminders. - - Returns: - A list of integer day-offsets (each ``>= 1``), e.g. ``[22, 15, 8]`` for - ``days=30, count=3``. Empty if there is no room for any reminder. - """ - # No room for any reminder (deadline today or already passed). - if days < 1 or count < 1: - return [] - - # Never more reminders than available days (one per day at most). - effective = min(int(count), int(days)) - - # A single reminder: put it one day before the deadline. - if effective == 1: - return [1] - - # Spread "effective" points evenly inside [1, days]. Using i/(effective-1) - # for i in 0..effective-1 gives fractions 0..1; map them onto [1, days]. - # This places the first reminder at the start of the period (offset ~days) - # and the last one one day before the deadline (offset 1). - offsets = set() - for i in range(effective): - frac = i / (effective - 1) # 0.0 .. 1.0 - # offset = days at frac 0 (start), 1 at frac 1 (just before deadline). - offset = round(days - frac * (days - 1)) - offset = max(1, min(days, offset)) - offsets.add(offset) - - # Sorted descending: earliest reminder (largest offset) first. - return sorted(offsets, reverse=True) - - -# Fixed reminder offsets (in days BEFORE the delivery date) used in BASIC mode. -# In BASIC the check-alive parameter is hidden/unmanaged, so reminders cannot be -# spread over it; instead the owner asked for three fixed reminders: 30, 10 and -# 1 day before the inheritance delivery date. -BASIC_REMINDER_OFFSETS = (30, 10, 1) - - -def basic_reminder_offsets(days_to_deadline): - """Return the BASIC-mode reminder offsets that still fall in the future. - - BASIC mode uses the fixed offsets in ``BASIC_REMINDER_OFFSETS`` (30, 10 and - 1 day before the delivery date). Any offset that would land in the past is - dropped, because a reminder before "today" is useless: if the delivery date - is only ``days_to_deadline`` days away, only the offsets that are ``<= - days_to_deadline`` are kept. - - Args: - days_to_deadline: whole days from now until the delivery date. - - Returns: - A list of integer day-offsets (each ``>= 1``), sorted as in - ``BASIC_REMINDER_OFFSETS`` (descending: earliest reminder first). Empty - when the delivery date is less than one day away. - """ - horizon = max(int(days_to_deadline), 0) - return [off for off in BASIC_REMINDER_OFFSETS if 1 <= off <= horizon] - - class ClickableLabel(QLabel): doubleClicked = pyqtSignal() @@ -219,39 +146,12 @@ class BalTxFeesWidget(QWidget): -class _LockTimeEditor: - min_allowed_value = NLOCKTIME_MIN - max_allowed_value = NLOCKTIME_MAX - alarm = None - - def get_value(self) -> Optional[int]: - raise NotImplementedError() - - def set_value(self, x: Any, force=True) -> None: - raise NotImplementedError() - - @classmethod - def is_acceptable_locktime(cls, x: Any) -> bool: - if not x: # e.g. empty string - return True - try: - x = int(x) - except Exception as _e: - return False - return cls.min_allowed_value <= x <= cls.max_allowed_value - - @staticmethod - def get_max_allowed_timestamp() -> int: - ts = NLOCKTIME_MAX - # Test if this value is within the valid timestamp limits (which is platform-dependent). - # see #6170 - try: - datetime.fromtimestamp(ts) - except (OSError, OverflowError): - ts = 2**31 - 1 # INT32_MAX - datetime.fromtimestamp(ts) # test if raises - return ts +class _LockTimeEditor(LockTimeEditor): + """Qt-side locktime editor base. + The pure acceptance bounds and helpers live in ``bal.core.input_rules``; + this is just the mixin that lets Qt widgets reuse them. + """ class BalTimeEditWidget(QWidget, _LockTimeEditor): @@ -572,51 +472,16 @@ class LockTimeRawEdit(QLineEdit, _LockTimeEditor): self.isyears = False self.time_edit = time_edit - @staticmethod - def replace_str(text): - """Strip the relative-time suffixes (d/y) from the text. - - Only days ("d") and years ("y") are supported. The block-height - suffix ("b") was removed (A1): locktimes are always timestamps now. - """ - return str(text).replace("d", "").replace("y", "") - - def checkbdy(self, s, pos, appendix): - try: - charpos = pos - 1 - charpos = max(0, charpos) - charpos = min(len(s) - 1, charpos) - if appendix == s[charpos]: - s = self.replace_str(s) + appendix - pos = charpos - except Exception: - pass - return pos, s - def numbify(self): # Only digits plus the day ("d") and year ("y") suffixes are accepted. # The block-height suffix ("b") was removed (A1): locktimes are always # UNIX timestamps now, so block-relative input is no longer allowed. + # The sanitisation itself lives in bal.core.input_rules. text = self.text().strip() - chars = "0123456789dy" pos = self.cursorPosition() - pos = len("".join([i for i in text[:pos] if i in chars])) - s = "".join([i for i in text if i in chars]) - self.isdays = False - self.isyears = False - - pos, s = self.checkbdy(s, pos, "d") - pos, s = self.checkbdy(s, pos, "y") - - if "d" in s: - self.isdays = True - if "y" in s: - self.isyears = True - - if self.isdays: - s = self.replace_str(s) + "d" - if self.isyears: - s = self.replace_str(s) + "y" + s, isdays, isyears, pos = normalize_locktime_raw_text(text, pos) + self.isdays = isdays + self.isyears = isyears self.blockSignals(True) self.setText(s) self.blockSignals(False) @@ -1073,11 +938,12 @@ class WillSettingsWidget(QWidget): sees several distinct appointments in their calendar. The number of events is read from the NUM_REMINDERS setting (default 3, - capped at 5 by the settings dialog). Their dates are computed with - ``compute_reminder_offsets``: the offsets are spread uniformly across the - check-alive period and the LAST event always falls one day before the - delivery deadline (locktime). If the period is shorter than the - requested number of reminders, at most one event per day is produced. + capped at 5 by the settings dialog). Their dates are computed by + ``bal.core.reminders.build_ics_reminders`` (offsets spread uniformly + across the check-alive period; the LAST event always falls one day + before the delivery deadline / locktime). If the period is shorter than + the requested number of reminders, at most one event per day is + produced. Each event: * is placed on ``locktime - offset`` days (its own visible date); @@ -1092,103 +958,23 @@ class WillSettingsWidget(QWidget): path the user picks in the save dialog (default name "BAL_will_event.ics" on the Desktop). """ - now = BalCalendar.format_time(datetime.now()) - - # locktime = delivery deadline. It is exposed by the date widget as - # ``.alarm`` and already reflects the (possibly auto-anticipated) minimum - # transaction locktime, so the calendar uses the correct delivery date. - locktime = self.widgets["locktime"].alarm - - # BASIC vs ADVANCED reminder strategy. - # - # In ADVANCED mode the reminders are spread uniformly across the - # check-alive (threshold) period, ending one day before the deadline. - # - # In BASIC mode the check-alive parameter is NOT shown nor managed by the - # user (it stays at an arbitrary default), so spreading reminders over it - # is meaningless. The owner asked that, in BASIC, the calendar simply - # saves the inheritance delivery date with three fixed reminders: 30 days - # before, 10 days before and 1 day before. We also drop any fixed offset - # that would fall in the past (a reminder before "today" is useless), so - # a short-dated will still gets the reminders that are still in the - # future. - if self.bal_window.bal_plugin.is_basic_mode(): - # Whole days from now until the delivery date. Fixed offsets (30, 10, - # 1 day before) are applied by basic_reminder_offsets, which also - # drops any offset that would fall in the past. - days_to_deadline = (locktime - datetime.now()).days - offsets = basic_reminder_offsets(days_to_deadline) - else: - # ADVANCED: spread reminders over the check-alive period as before. - threshold = self.widgets["threshold"].alarm - # Whole days available between check-alive and the deadline. - days = (locktime - threshold).days - # How many reminders the user asked for (default 3 if unreadable). - try: - count = int(self.bal_window.bal_plugin.NUM_REMINDERS.get()) - except Exception: - count = 3 - # Day-offsets BEFORE the deadline, e.g. [30, 16, 1]. The list always - # ends with 1 (one day before the locktime) when >= 2 reminders fit. - offsets = compute_reminder_offsets(days, count) - - # Per-event heir details and the shared description/summary templates. - heirs_details = "\r\n".join( - f" {heir} - {self.bal_window.heirs[heir][0]}, {self.bal_window.heirs[heir][1]}" - for heir in self.bal_window.heirs - ) - # BASIC mode: use factory defaults (the hidden settings are ignored). - # ADVANCED mode: use the user-configured values. - if self.bal_window.bal_plugin.is_basic_mode(): - raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.default - raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.default - else: - raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.get() - raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.get() - event_description = BalCalendar.ical_escape( - f"{raw_description}" - .replace("$wallet_name", str(self.bal_window.wallet)) - .replace("$heirs_complete", heirs_details) - ) - summary_base = ( - f"{raw_summary}" - .replace("$wallet_name", str(self.bal_window.wallet)) - ) - - lines = [ - "BEGIN:VCALENDAR", - "VERSION:2.0", - f"PRODID:-//Bitcoin After Life//Electrum Plugin/{self.bal_window.bal_plugin.version}", - ] - - # One separate VEVENT per reminder offset (its own date in the calendar). - total = len(offsets) - for idx, offset in enumerate(offsets, start=1): - # The visible date of this event: "offset" days before the deadline. - event_dt = BalCalendar.format_time(locktime - timedelta(days=offset)) - # Suffix the summary so the N events are easy to tell apart. - summary = BalCalendar.ical_escape( - f"{summary_base} (reminder {idx}/{total})" + # The .ics content (offsets, escaping, folding, VEVENT layout) is built + # by the pure ``build_ics_reminders`` in bal.core.reminders. When no + # reminder falls in the future it returns None (ToDo #2) and the user is + # warned instead of getting an empty-looking file. + ics_content = self._ics_provider() + if not ics_content: + self.bal_window.show_warning( + _( + "No reminders were saved: the delivery date is too " + "close (or already passed)" + ) ) - lines.extend([ - "BEGIN:VEVENT", - # Offset in the UID keeps each event unique (no merging). - f"UID:bal-{str(self.bal_window.wallet)}-{offset}d", - f"DTSTAMP:{now}", - f"DTSTART:{event_dt}", - f"DTEND:{event_dt}", - f"SUMMARY:{summary}", - f"DESCRIPTION:{event_description}", - "END:VEVENT", - ]) + return - lines.append("END:VCALENDAR") - - lines = [s.rstrip("\r\n") for s in lines] - ics_content = "\r\n".join(lines) + "\r\n" # Keep the generated .ics in a temp file; it is copied to the path the # user picks below. - self.temp_path = BalCalendar.write_temp_ics(ics_content) + self.temp_path = write_temp_ics(ics_content) # Group D / D1b: always ask the user WHERE to save the .ics file (the # plugin no longer tries to open it with a calendar app). The save @@ -1241,26 +1027,31 @@ class WillSettingsWidget(QWidget): def _ics_provider(self): """Return the .ics content for the current locktime/threshold values. - Used by :class:`BalCalendarButton` as its content provider. + Used by :class:`BalCalendarButton` as its content provider. The whole + document (reminder offsets, escaping, folding, VEVENT layout) is built + by the pure :func:`bal.core.reminders.build_ics_reminders`. """ - from datetime import datetime, timedelta - try: locktime = self.widgets["locktime"].alarm - if self.bal_window.bal_plugin.is_basic_mode(): - days_to_deadline = (locktime - datetime.now()).days - offsets = basic_reminder_offsets(days_to_deadline) + basic_mode = self.bal_window.bal_plugin.is_basic_mode() + if basic_mode: + # BASIC mode: use factory defaults (the hidden settings are + # ignored) and the fixed 30/10/1 offsets. + raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.default + raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.default + threshold = None + num_reminders = 3 else: + raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.get() + raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.get() threshold = self.widgets["threshold"].alarm - days = (locktime - threshold).days try: - count = int(self.bal_window.bal_plugin.NUM_REMINDERS.get()) + num_reminders = int( + self.bal_window.bal_plugin.NUM_REMINDERS.get() + ) except Exception: - count = 3 - offsets = compute_reminder_offsets(days, count) - - now = BalCalendar.format_time(datetime.now()) + num_reminders = 3 heirs_details = "\r\n".join( f" {heir} - {self.bal_window.heirs[heir][0]}, " @@ -1268,50 +1059,17 @@ class WillSettingsWidget(QWidget): for heir in self.bal_window.heirs ) - if self.bal_window.bal_plugin.is_basic_mode(): - raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.default - raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.default - else: - raw_description = self.bal_window.bal_plugin.EVENT_DESCRIPTION.get() - raw_summary = self.bal_window.bal_plugin.EVENT_SUMMARY.get() - - event_description = BalCalendar.ical_escape( - f"{raw_description}" - .replace("$wallet_name", str(self.bal_window.wallet)) - .replace("$heirs_complete", heirs_details) + return build_ics_reminders( + locktime=locktime, + basic_mode=basic_mode, + description=raw_description, + summary=raw_summary, + wallet_name=str(self.bal_window.wallet), + heirs_details=heirs_details, + version=self.bal_window.bal_plugin.version, + num_reminders=num_reminders, + threshold=threshold, ) - summary_base = ( - f"{raw_summary}" - .replace("$wallet_name", str(self.bal_window.wallet)) - ) - - lines = [ - "BEGIN:VCALENDAR", - "VERSION:2.0", - "PRODID:-//Bitcoin After Life//Electrum Plugin/" - f"{self.bal_window.bal_plugin.version}", - ] - - total = len(offsets) - for idx, offset in enumerate(offsets, start=1): - event_dt = BalCalendar.format_time(locktime - timedelta(days=offset)) - summary = BalCalendar.ical_escape( - f"{summary_base} (reminder {idx}/{total})" - ) - lines.extend([ - "BEGIN:VEVENT", - f"UID:bal-{str(self.bal_window.wallet)}-{offset}d", - f"DTSTAMP:{now}", - f"DTSTART:{event_dt}", - f"DTEND:{event_dt}", - f"SUMMARY:{summary}", - f"DESCRIPTION:{event_description}", - "END:VEVENT", - ]) - - lines.append("END:VCALENDAR") - lines = [s.rstrip("\r\n") for s in lines] - return "\r\n".join(lines) + "\r\n" except Exception as e: _logger.error(f"failed to generate .ics: {e}") return None @@ -1354,40 +1112,20 @@ class PercAmountEdit(BTCAmountEdit): super().__init__(decimal_point, is_int, parent, max_amount=max_amount) def numbify(self): + # The text sanitisation lives in bal.core.input_rules. text = self.text().strip() if text == "!": self.shortcut.emit() return pos = self.cursorPosition() - chars = "0123456789%" - chars += DECIMAL_POINT - - s = "".join([i for i in text if i in chars]) - - if "%" in s: - self.is_perc = True - s = s.replace("%", "") - else: - self.is_perc = False - - if DECIMAL_POINT in s: - p = s.find(DECIMAL_POINT) - s = s.replace(DECIMAL_POINT, "") - s = s[:p] + DECIMAL_POINT + s[p : p + 8] - if self.is_perc: - s += "%" + s, self.is_perc = normalize_perc_amount_text(text, DECIMAL_POINT) self.setText(s) self.setModified(self.hasFocus()) self.setCursorPosition(pos) def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]: - try: - text = text.replace(DECIMAL_POINT, ".") - text = text.replace("%", "") - return (Decimal)(text) - except Exception: - return None + return parse_perc_amount(text, DECIMAL_POINT) def _get_text_from_amount(self, amount): out = super()._get_text_from_amount(amount) diff --git a/bal/gui/qt/window.py b/bal/gui/qt/window.py index afbfa58..880d386 100644 --- a/bal/gui/qt/window.py +++ b/bal/gui/qt/window.py @@ -14,8 +14,16 @@ The actual Bitcoin logic lives in :mod:`bal.core`; this class only coordinates it with the GUI. """ +import json import threading +from electrum.util import MyEncoder + +from ...core.checkalive import ( + CheckAliveError, + check_alive_expired, + resolve_date_to_check, +) from .common import * from .common import _, _logger # underscore names are not re-exported by "import *" from .dialogs import ( @@ -123,7 +131,22 @@ class BalWindow: for k in keys: del self.will[k] for wid, w in self.willitems.items(): - self.will[wid] = w.to_dict() + d = w.to_dict() + # Store the tx in its serialized form: the wallet DB deep-copies + # every value on save (JsonDB.put), and a live Transaction carrying + # wallet-derived input info (utxo / script_descriptor, which hold a + # threading.RLock) cannot be deep-copied. Serializing to a string + # matches how the will is re-read (get_will -> tx_from_any). + d["tx"] = str(d["tx"]) + # Mirror the encoder the wallet DB uses: JsonDB.put returns False + # (silently dropping the will) when a value cannot be serialized, + # so prove it here and fail loudly instead. + try: + json.dumps(d, cls=MyEncoder) + except Exception as e: + _logger.error(f"save_willitems: will {wid} is not serializable: {e!r}") + raise + self.will[wid] = d def init_will(self): _logger.info("********************init_____will____________**********") @@ -614,11 +637,11 @@ class BalWindow: # against the current moment, i.e. the Check Alive effectively does # not exist, while the delivery time (locktime) is still fully # enforced. ADVANCED mode keeps the user-controlled Check Alive - # exactly as before. - if self.bal_plugin.is_basic_mode(): - self.date_to_check = datetime.now().timestamp() - else: - self.date_to_check = BalTimestamp(self.will_settings['threshold']).to_timestamp() + # exactly as before. The policy itself lives in + # ``bal.core.checkalive.resolve_date_to_check``. + self.date_to_check = resolve_date_to_check( + self.bal_plugin.is_basic_mode(), self.will_settings + ) # found = False # NOTE: block-height tracking removed (A1) - locktimes are always # UNIX timestamps now, so we no longer read the current block height @@ -635,9 +658,10 @@ class BalWindow: # flow. In BASIC we therefore SKIP this check entirely, so a passed # check-alive date never forces a postpone/rewrite of the will. The # delivery time (locktime) is unaffected and still fully enforced. - if ( - not self.bal_plugin.is_basic_mode() - and self.date_to_check < datetime.now().timestamp() + # The BASIC/ADVANCED rule lives in + # ``bal.core.checkalive.check_alive_expired``. + if check_alive_expired( + self.bal_plugin.is_basic_mode(), self.date_to_check ): raise CheckAliveError(self.date_to_check) @@ -945,7 +969,12 @@ class BalWindow: targets = Will.only_valid(willitems) for txid in targets: wi = willitems[txid] - tx = copy.deepcopy(wi.tx) + # Do NOT deepcopy: the stored tx carries wallet-derived objects + # (utxo / script_descriptor) that hold a threading.RLock, and + # copy.deepcopy raises "cannot pickle '_thread.RLock'". Re-parse + # from the serialized form instead, which is exactly how the will + # is persisted/loaded (WillItem.to_dict -> serialize -> tx_from_any). + tx = Will.get_tx_from_any(str(wi.tx)) if wi.get_status("COMPLETE"): txs[txid] = tx continue @@ -1057,7 +1086,10 @@ class BalWindow: def on_success(txs): if txs: for txid, tx in txs.items(): - willitems[txid].tx = copy.deepcopy(tx) + # Re-parse instead of deepcopy: the signed tx may carry + # wallet-derived input info holding a threading.RLock, which + # copy.deepcopy cannot pickle (see sign_transactions above). + willitems[txid].tx = Will.get_tx_from_any(str(tx)) if not external: self.will[txid] = willitems[txid].to_dict() try: @@ -1251,7 +1283,9 @@ class BalWindow: # very first action in a session). Fall back to "now" so the local # validity check and the trailing update_all() always have it. if not hasattr(self, "date_to_check") or self.date_to_check is None: - self.date_to_check = datetime.now().timestamp() + self.date_to_check = resolve_date_to_check( + self.bal_plugin.is_basic_mode(), self.will_settings + ) for wid, wi in imported.items(): if wid in self.willitems: diff --git a/tests/karen7 b/tests/karen7 index 08b8700..24f0629 100644 --- a/tests/karen7 +++ b/tests/karen7 @@ -28,6 +28,10 @@ [ "2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c", 2279 + ], + [ + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe", + 2413 ] ], "bcrt1q0tf3hnml0s03rsqfjzmm4tlnxu9k0969yqvhn9": [ @@ -717,6 +721,7 @@ 710 ] ], + "bcrt1qaxj23v6mp5tj2wyvzh0p0xuu0jhh74vjkk3dnu": [], "bcrt1qazle627r46apscly8lj4q5cxfxgrtuew879jde": [ [ "8ec1f1de03d4af8a3a22bc479fec5b766f4499d955def32d8aa58b8873ecf9fe", @@ -1156,6 +1161,7 @@ 710 ] ], + "bcrt1qggc9a8nf9vjnjrru8v869zswhycajrsudfz4g4": [], "bcrt1qghmsds7h0r00nqx4m38jjn43fl6h93pkxurfz5": [ [ "77270fdd97f95bfaf6bd3132246ec2aba1ff9a0dfb92c87791aee18cad6b0011", @@ -1186,7 +1192,12 @@ 734 ] ], - "bcrt1qgv0wu4v6kjzef5mnxfh2m9z6y7mez0ja0tt8mu": [], + "bcrt1qgv0wu4v6kjzef5mnxfh2m9z6y7mez0ja0tt8mu": [ + [ + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326", + 2438 + ] + ], "bcrt1qgx5kpesp2c6rlckln2vrmyt3xgvkj8pafx42tw": [ [ "683425171b094d3e222a7eb6327e2276e8a66f02314d57cd3c50271eaef8d3f9", @@ -2183,7 +2194,16 @@ 734 ] ], - "bcrt1qwxlea0j59zl6vx54apa072l89q7zemz5fj5tea": [], + "bcrt1qwxlea0j59zl6vx54apa072l89q7zemz5fj5tea": [ + [ + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe", + 2413 + ], + [ + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326", + 2438 + ] + ], "bcrt1qx2tr9392tlw33vffx47qkx3hk8vqt07223w8jt": [ [ "83ff9433dece51bf3a37f60c81e196fac96cef88c587a2f97fb44be490482536", @@ -2445,7 +2465,9 @@ "bcrt1q4wle4rjunlheyrpx3cgewjry84lt9vcfn6dzm7", "bcrt1qn69fe8fjlwyx0eqhzxl4wmypj8re3j6sq5m9zk", "bcrt1quykurwfx3strtkezdvvkffalncgpx85p83w9v4", - "bcrt1q08atkh7p3xu5cn4azclc7tcuuv7332kmjfjjlr" + "bcrt1q08atkh7p3xu5cn4azclc7tcuuv7332kmjfjjlr", + "bcrt1qaxj23v6mp5tj2wyvzh0p0xuu0jhh74vjkk3dnu", + "bcrt1qggc9a8nf9vjnjrru8v869zswhycajrsudfz4g4" ], "receiving": [ "bcrt1qpm5utekdtmzwnlkh7jq5497vwwf6sm38tljan5", @@ -2771,12 +2793,14 @@ "10617202b6e0856c5b015e1f882b7ee51ad7edf85ab1d0f7764b5e8eb70775d5": "BAL Transaction", "11a7d226afc8d5fe63f9cc00b7edb5a8e4dd8444e08930d1bbe77e8d14171cd2": "BAL Transaction", "11b6ce930c0c09f264cf9ce9ee5ff174c1550673fc65cff3d3cffac86ef8ea8e": "BAL Transaction", + "11e2628fbdf54a7484b99124b2bc8f9ade3549d2eedcae72dd42cded7f9ac4eb": "BAL Inheritance transaction", "1210e8189eed90143785def33b65240deab8b2c051526a267aec313762ebe295": "BAL Transaction", "12e8be720f97a0e449e047d1eb3b8b5700d47112737c3acb894a98e33fd3598d": "BAL Inheritance transaction", "13998d34fcafd09ac0740f728d0381233e9ec59628785e8e3d4ce79e59232ac7": "BAL Transaction", "1403ff43f52ec3cf73892c429bb7c0c913814b01f87fff5d71f2f10f240d6dfb": "BAL Transaction", "150945af0c56e570acd35c4c003f074c775d20aafc2f6a20beaf380f5e3d5ecd": "BAL Transaction", "152f2c51c58697507f03641069d3100923f06bdc77c10de9b85e21b59986d458": "BAL Transaction", + "15c42573436162f794bd007039f5930cf3fdd3bb8c9362840c127f7b398ff337": "BAL Invalidate transaction", "15f7dda988d4171681de6892a19934c81d86121f20fff6f9b3a00c1f4174a392": "BAL Transaction", "1623f1892736ae0e057b43760d813a82f7a6dbc39ef1aeb050dc0176eb89c26a": "BAL Transaction", "172dcc6519f6381112614ad40e4b743f2cb7b3217ce1f3965539c83d2a9ac773": "BAL Transaction", @@ -2808,6 +2832,7 @@ "27674bbe4485cb5f4d869124814f6d643201bcd13599b60dcaf693550cb0cdcc": "BAL Invalidate", "27a70f96b10b49e4dccccabc27d6b75516bfa5779ec53c3aa02cacb6d7c2c39b": "BAL Transaction", "27d8bafd4f7be6a086af8ffc6ce0fb50588c035669582beed48538032bc7d50d": "BAL Transaction", + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326": "BAL Invalidate transaction", "28b432660bc1232de270461ec9639c07d4438d77f0c2571cede1f92351254a9a": "BAL Transaction", "28e0ba1ecf9c349345c67317a15fb7e53193dfb1657170b32e4dd8354261bdab": "BAL Transaction", "2967d7c89ad9c988524978a11d5fa51aa16269049c57dce4d01baba990e2d95d": "BAL Inheritance transaction", @@ -2908,6 +2933,7 @@ "641c271d8afa23eeb708a2b517640992fdcca1f6554cb68862f1e9237404b68f": "BAL Transaction", "649088a6e3629a72aae112903c62eab2cfb4d89cb824e99f66320b1dbba5c258": "BAL Transaction", "66627bf44c45314fca32195df8dfa6fa6a950a6f6492a2192d18f292b5c441bd": "BAL Invalidate", + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe": "BAL Invalidate transaction", "6698bda498953e55b81e114aee06b876f547225d90e388dc4327577e591e3e26": "BAL Transaction", "66a8b89e1d14a0a4d100740f152bc187ecb28c5c6b38a5a4137f53531f362439": "BAL Transaction", "66e35bcf24f86540d0d8f0bd12706ddab93727ba7dfb63834f9d1537b23068c4": "BAL Transaction", @@ -2954,6 +2980,7 @@ "7d25d1b0e753475729b42391e9e4f8c6afdd5055d54ea18c92b4d7db5f6dda78": "BAL Transaction", "7d5aaab55aa767a943c343226819a9ed404a0de5b4c795b525b347b51c5301bb": "BAL Transaction", "7e6926289bea6a005a09b7a11e8d7b7a74397874de74920741501ddcd9189da5": "BAL Transaction", + "7e78ec1cb9c9ae8a948b2a3329edd9b48986752a6a9e86dc080e764d21945e4f": "BAL Invalidate transaction", "7f70ff6a1ad00685939cd5db836bd6822314d7caaa3cb03431bb2251de578388": "BAL Transaction", "8041b7ba7543b2d7dd21198210a5efc2c03ecfc4378d0b8a20aa3de0799b12ca": "BAL Transaction", "808dc784760bf9e9e52b390a54a2a07720659b51b10df6b98dce83368c39fe08": "BAL Transaction", @@ -3103,6 +3130,7 @@ "dd1536302b7321252eb68fa30bdbeb13989934c1bc946f005dafbe8bc8c2b517": "BAL Transaction", "de35722e384e4903dd5551357d9276e3fa1f7c9b1c97ad943e4bd91fa9d52ed9": "BAL Transaction", "decb3c083ce18a76b8371a4533b90a094ce8cbb0e2cc64d17071a51076ed1cc6": "BAL Transaction", + "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8": "BAL Inheritance transaction", "df032c7e361179bccd7b93a72d98d81872d29d704c2444acc3db7a4485f81e02": "BAL Transaction", "df31987dd7c3dc785606d3149d58dbed33317b959fbcd5e3037bd2375cf93953": "BAL Transaction", "df4e9dc8f6442c08ef66b16e5c38da3f611e27b4171c5fad42d7a21cb458eb0b": "BAL Transaction", @@ -3188,7 +3216,7 @@ "lightning_xprv": "vprv9HMaVA1cGK7XCUCiTrrdD9kGgiSeiwpqDDYfAafTZoSXyZtmQYnQ4CUvsZggS4fWrF3kve47MFWjrWLJ6t4uXjzs2XagtmBeUqoaRRFpJGF", "notes_text": "", "num_parents": { - "2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c": 4 + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326": 6 }, "onchain_channel_backups": {}, "payment_requests": { @@ -3704,6 +3732,9 @@ "5846748d35cc1f8b447cc00f0f3dd3de9d8c6647f3c74e736dfe2c14ae9fb22d": { "42a1d1b8b6fbfa6b9f1158c2663e00c023a278d9c587901a40ca77fb5835e471:0": 1250000000 }, + "596e445351c65bcdef2637da42474ca7e283a1103b1366e1afc548701f68fa3f": { + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe:0": 999999560 + }, "5a5b19468ec2a7ce43bfa32b7128296e6f08fb01a0113fb50ca2324988b901ec": { "83d64baa165f011c9c6902880c2fefada3df75c1271a51da205e383cc6d58327:0": 1250000000 }, @@ -3818,6 +3849,9 @@ "81316109befd2d543582d68b9cbb65a9c1bc215d16791b6b546d50134aaac363": { "ab74079fca87f186df5676c62de63d8b21e98746c8c8888f108310bc7849e70e:0": 2500000000 }, + "8149fa7c3ec75c7ea3075adae72f7d7119acadae6af68b90d7c1174a669eff22": { + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326:0": 999999450 + }, "8206833de2ac45d066b04a520a4bb776b8dc473d7d9be96a7c4c2ebf0a749786": { "13c4b8b888b4c7f68076855c11d51eace7ee8d6c106142b493cf47a65a8f5be1:0": 1250000000 }, @@ -4380,6 +4414,9 @@ "2af047c4a003fb7b90ce2c8fc49bcdb3eaee0fff213b14dc1645b9fb18b8f0f6": { "0": "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308" }, + "2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c": { + "0": "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe" + }, "2c6d3a41eb0f5589e2586138b2bee1cf453144fe48dc73c2a3b0f4d0a3bfd596": { "0": "5c6d7a19cfb35a98949a25c5fede1dd798a7e1509e0067d000319126c34f8c15" }, @@ -4585,6 +4622,9 @@ "64f40121401bcfa201bf59dc9c8d23e2fe89da362685bf64694eb98f6f4fc991": { "0": "5c6d7a19cfb35a98949a25c5fede1dd798a7e1509e0067d000319126c34f8c15" }, + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe": { + "0": "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326" + }, "683425171b094d3e222a7eb6327e2276e8a66f02314d57cd3c50271eaef8d3f9": { "0": "5c6d7a19cfb35a98949a25c5fede1dd798a7e1509e0067d000319126c34f8c15" }, @@ -5169,7 +5209,7 @@ "2": "8cebb4ea3a63829a2787c3a473f14b4f73ced07552f73ff8c2b1152169042550" } }, - "stored_height": 2381, + "stored_height": 2455, "submarine_swaps": {}, "transactions": { "00fda0d8fdc53b1f95410bafca884c5dff8ac1e5c168ace5f09a32d872b7413a": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402460100ffffffff02807c814a00000000160014b51c529851d6140f1a37f2aa46bafe171a549f210000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", @@ -5207,6 +5247,7 @@ "211e0cb1f99e931a517437fe2b1ea81e97332b8abf066a99704da17bcbeabb43": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04026c0100ffffffff02807c814a000000001600146b6f2c9545e188f8e79b25d091b8e0a20af8364b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "26171dce2366ca96fdcb0749bab05fa8b5abbf9b3a4dae1b9ae6f7f5425108b1": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402690100ffffffff02807c814a0000000016001494b9a93c92e060c47bbccffcd29c9cc56b9da05c0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "267eeb8e65681ffaeafe0a04616b270750f1f0a83e5d51f82974ad0c98e2c5a6": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402d10100ffffffff0240be40250000000016001498b1a3d729e96088cc6c83408b36fa1382c4f4f30000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326": "02000000000101be5e0d37d1d9583a2c8e2dffd59192f663d85af9edfce4fcb2f129aea8be72660000000000fdffffff01dac79a3b00000000160014431eee559ab48594d373326ead945a27b7913e5d0247304402201c45c2dac9ba31b9905aada8f44a676397a8ed56df73c81f17e4baf34d4a2e4d022078598b32228eedefc5a89be1f123d5139041a56579c499bcbafd7b5c1a24a0fa012102825bd3669bd57813180d972def557b09b204c631fed9654af4870c80130ddf1885090000", "282778a46251bd4de17361ee948d6ca274ee0668b190398ca85fd939adb59d55": "0200000000010174e4465944e8dd66f59e82e22eebb10e8387a5527618073a285b29f9d9735d330200000000fdffffff01445b2015200000001600145533cc833ce8cd1408ff3c9bd860351d69ab95040247304402207b7509ead1f9f26b005fdeb91bac1d5d77a590bd903aa006453ce65b8f54e8fc022069755b2c93d42e69cc9c4216e43dd7d85403d0e788c56caa0d4e6ab55b5246550121038d7efb75df24733109903da38632c3a3404bc80451d29524a96b623a7af5921e8b030000", "28837747eb6a849543f5f03063f1436fc44f64f028448022ec0c382ac5bb7c31": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402500100ffffffff02807c814a000000001600147ac0abff0d5ffafcb01f9df3cfed0bbc741854b80000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "2a1b54803e6649bb49778d2b84b542c5db10351f075941c7ed6453e0c152ea39": "02000000000101f50cec494750062daf2f321231228cc0cf35f77d25c3f55815c5bcd0f42a935e0000000000fdffffff0250c30000000000001600140ee9c5e6cd5ec4e9fed7f4814a97cc7393a86e27a65622000000000016001488be8e07f7edee0da5b5db16d8c89f408c055f2b0247304402201859ab99d7bce45216ba1083efbd0511ee5ffac7628bfb63fdd5f3993bb1d80e02206ddf15ca3e6537076823c8176094db3ff901a7ec91632e5d23e1ec4258c47c95012102159c4641474a4d8fff2f454af57f4db23cffaabfd2f93ca4c99e9318a0bf540738070000", @@ -5274,6 +5315,7 @@ "642e892416a039224ad50e987bfbfecefee57103dd39c6833e2992cebf8a2e8d": "02000000000101609438ba82251a8cf3acfd1f189369029290cf2f8793b2b8f3c4c363c8e6c1a30000000000fdffffff01296a364030000000160014d30dc6386b39a982f0bc51494e03c91c3037b11802473044022035bb5c21bc90400ef14bc8b0b9c89de15979b091b222ee645b8c0cfad289e3c0022056d38d2157fbeef74a2af3307120586e29a873a51c6e71cb41becf53d410da3f012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b300000000", "6444792f99c990677950a6e8c27a18eb4aa1eb946763887cd8c67d7495cbd1dc": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402220100ffffffff0200f9029500000000160014e23a95111d6159da1219fc9dd9107b486f6399970000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "64f40121401bcfa201bf59dc9c8d23e2fe89da362685bf64694eb98f6f4fc991": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04022d0100ffffffff02807c814a00000000160014eb6b5ae76ee9273ec6e61f72db27988605839d280000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe": "020000000001014c4fffdabe4d27a8b06618492a5fe828345551b3da8107b8cada5f452995652c0000000000fdffffff0148c89a3b0000000016001471bf9ebe5428bfa61a95e87aff2be7283c2cec5402473044022028734896d1f74344516e024fbf106124db3f521b67caead7ff929601a1d0d04d02204d70dac814580261157f0e5808f9d488ad369979dcd083b4adfdb4fc1d80dd550121032592689364f3a4796fc35a1f183791eb7c5db25e6f36fa0f1e92654ed679c7916c090000", "683425171b094d3e222a7eb6327e2276e8a66f02314d57cd3c50271eaef8d3f9": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402490100ffffffff02807c814a0000000016001441a960e60156343fe2df9a983d91713219691c3d0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "68a99483af54769940b80095e83d95d607f9c32c2d03c0b89a86cd4143d65eb9": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402d10700ffffffff022f50090000000000160014ed834d872f7e3f5eaacab4fc5249df650e626fa60000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "68b9c5bddb83e4f54192b67a978f6bea1e305ec558c1042623d6bcc54188e135": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402f50200ffffffff02902f500900000000160014ea779f0f8e203034ae25cd71e52c1a81ac488e2b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", @@ -5626,6 +5668,11 @@ false, 1 ], + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326": [ + 110, + true, + 1 + ], "282778a46251bd4de17361ee948d6ca274ee0668b190398ca85fd939adb59d55": [ 1099890, true, @@ -5961,6 +6008,11 @@ false, 1 ], + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe": [ + 110, + true, + 1 + ], "683425171b094d3e222a7eb6327e2276e8a66f02314d57cd3c50271eaef8d3f9": [ null, false, @@ -6870,6 +6922,11 @@ "0fb9da0e14c71d8b85bf00d23fa3b599972ea48f94e567e0f9733ed7357ba686:2": 137794696378 } }, + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326": { + "bcrt1qwxlea0j59zl6vx54apa072l89q7zemz5fj5tea": { + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe:0": 999999560 + } + }, "282778a46251bd4de17361ee948d6ca274ee0668b190398ca85fd939adb59d55": { "bcrt1qh2c83yulvs7kgw0g6q3lkxqws4cnf0uxpcgcpt": { "335d73d9f9295b283a07187652a587830eb1eb2ee2829ef566dde8445946e474:2": 137794495414 @@ -7260,6 +7317,11 @@ "a3c1e6c863c3c4f3b8b293872fcf9092026993181ffdacf38c1a2582ba389460:0": 207235749153 } }, + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe": { + "bcrt1q0ma5z02ma7q0cq7j49wz66clf69xw3fq2zl05n": { + "2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c:0": 999999670 + } + }, "697895649863c50517092f62b47284ab00dd812a100a2ca6ac9469b5e4f6da2b": { "bcrt1qazle627r46apscly8lj4q5cxfxgrtuew879jde": { "b6e993fb2b0584bb35b8eefcfbe6ba7c9b9b7875e69389063c96f78dd1fca9e1:1": 50649701700 @@ -8008,6 +8070,14 @@ ] } }, + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326": { + "bcrt1qgv0wu4v6kjzef5mnxfh2m9z6y7mez0ja0tt8mu": { + "0": [ + 999999450, + false + ] + } + }, "282778a46251bd4de17361ee948d6ca274ee0668b190398ca85fd939adb59d55": { "bcrt1q25eueqeuarx3gz8l8jdascp4r456h9gydxmezx": { "0": [ @@ -8568,6 +8638,14 @@ ] } }, + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe": { + "bcrt1qwxlea0j59zl6vx54apa072l89q7zemz5fj5tea": { + "0": [ + 999999560, + false + ] + } + }, "683425171b094d3e222a7eb6327e2276e8a66f02314d57cd3c50271eaef8d3f9": { "bcrt1qgx5kpesp2c6rlckln2vrmyt3xgvkj8pafx42tw": { "0": [ @@ -10255,6 +10333,12 @@ 0, "79074b9f422009b5260706bb2fb943267b562284205df412901c048d1d8f8fb8" ], + "27e579cf0329d3a7ac6ee3cce36de4532911829d71bb2c6bb2c17d3084fd4326": [ + 2438, + 1785952526, + 1, + "10486dd0f7712166482261e685320b732ecb245a39c2040ab8cbc6d8c480638b" + ], "282778a46251bd4de17361ee948d6ca274ee0668b190398ca85fd939adb59d55": [ 908, 1770380931, @@ -10657,6 +10741,12 @@ 0, "54e5b9d88869c451c66306d661feb802bdbea5078f46bad551d03eb352993e29" ], + "6672bea8ae29f1b2fce4fcedf95ad863f69291d5ff2d8e2c3a58d9d1370d5ebe": [ + 2413, + 1785872632, + 1, + "71d17faedbcf2e69598e039dfca0476eecce1c2f227e939f66227a6a9b4c51bb" + ], "683425171b094d3e222a7eb6327e2276e8a66f02314d57cd3c50271eaef8d3f9": [ 329, 1761910284, @@ -11704,91 +11794,10 @@ }, "wallet_type": "standard", "will": { - "04a14aa60b6ef0c727fae209070dcb7926389aea0ef643ceaaf788c2c6e8ea1f": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "04a14aa60b6ef0c727fae209070dcb7926389aea0ef643ceaaf788c2c6e8ea1f", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460136260 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315422362 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460136260 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 20000, - 1807848000, - 20000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Updated.Replaced.Invalidated", - "time": 1781960175.6909134, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05204e000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc9a0ab5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d445bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc445bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022036cb123a7272c69e1bb485912617d0ae587c01fbbbeba14a2d77803a6119ca5c022068db54ab23b601f494c31c6922fcda7b5074d7d20510c4a32f2acde3df8d5039012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 20000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05204e000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc9a0ab5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d445bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc445bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022036cb123a7272c69e1bb485912617d0ae587c01fbbbeba14a2d77803a6119ca5c022068db54ab23b601f494c31c6922fcda7b5074d7d20510c4a32f2acde3df8d5039012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b\n020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc3075000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1afeb4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dfc4dc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcfc4dc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402204806fcb89b45a541d7a48355beece24af53ce4a0d40c94390929e8bfd393a9b5022000f1d4d4ad4bed3d323773f8bff93481c1a125cfa1e67d737c7e83519be6fdf8012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b\ncHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA\n020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d9af1b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9db440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcb440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402200e8ed269cd0443b9259ac666ca8a349f44aa4607a935fdf6f9bceebe1cc69bfd0220450cf86be10e9454abeb2815ed84960e1d45e5d489a43fe4c84bb2f943ae6975012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b\ncHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA\n", - "txsids": [ - "04a14aa60b6ef0c727fae209070dcb7926389aea0ef643ceaaf788c2c6e8ea1f", - "12e8be720f97a0e449e047d1eb3b8b5700d47112737c3acb894a98e33fd3598d", - "3318599c8ac525d9f9a29c298a943ae887afbf31ed92b781ceaa547e3ddd352b", - "5e9355915428b150861ede28eb86cfd17692e063f495657c3fdedc267a95e20b", - "b708916e8f8ab4562bb415f6876276e3901231649a9ed7f2d22ed0bae65cf0b9" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "051f5bb1b0df3f8e5011e71449646a1c6595b4e41a0252afbfbe92489a6c5043": { + "11e2628fbdf54a7484b99124b2bc8f9ade3549d2eedcae72dd42cded7f9ac4eb": { "ANTICIPATED": false, "BROADCASTED": false, - "CHECKED": false, + "CHECKED": true, "CHECK_FAIL": false, "COMPLETE": true, "CONFIRMED": false, @@ -11799,1336 +11808,84 @@ "INVALIDATED": true, "MEMPOOL": false, "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "051f5bb1b0df3f8e5011e71449646a1c6595b4e41a0252afbfbe92489a6c5043", - "baltx_fees": 100, - "change": null, - "description": "aaaa\nlucia\nmario\nmario2", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 58716792428 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 55262863462 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 58716792428 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "20%", - "300d", - 34539289663 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Replaced.Invalidated", - "time": 1781888638.550394, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff043fbcb30a080000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6660ecdd0c0000001600147e19af296f25d092f23a0c208823e65c81a2af9d6c26cbab0d0000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6c26cbab0d0000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402206cbd2596c9f7f855e7f64338b8e098c33da609b01e124f4ba56083536a354992022075b419be3b8fe981ecea5281ef007eeb49b688d50cf6147186c3db1b444de43b012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c040c06b", - "willexecutor": false - }, - "0f864bd74f0a66410e4c251f0993a61837f01ca947ce1badf0bb57e4b38e6866": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, + "PUSHED": true, "PUSH_FAIL": false, "REPLACED": false, "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "0f864bd74f0a66410e4c251f0993a61837f01ca947ce1badf0bb57e4b38e6866", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460137314 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "120d", - 66315423354 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460137314 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - "120d", - 40001, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Updated.Firmato.Invalidated", - "time": 1781968114.0018213, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff04419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc7a0eb5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d625fc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc625fc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220352e03b4c6bd7659078562b7a38e6fe0110995a14d7c1a8752de590c10cf7b7702202899f837837a63c697024e1c88fa77c868c9c71fd41c3f39ed9cfbce0fcc18bc012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c0f2d26a", - "willexecutor": false - }, - "12e8be720f97a0e449e047d1eb3b8b5700d47112737c3acb894a98e33fd3598d": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "12e8be720f97a0e449e047d1eb3b8b5700d47112737c3acb894a98e33fd3598d", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460132860 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315419162 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460132860 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 30000, - 1807848000, - 30000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Updated.Replaced.Invalidated", - "time": 1781962486.7599566, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc3075000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1afeb4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dfc4dc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcfc4dc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402204806fcb89b45a541d7a48355beece24af53ce4a0d40c94390929e8bfd393a9b5022000f1d4d4ad4bed3d323773f8bff93481c1a125cfa1e67d737c7e83519be6fdf8012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 30000, - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "url": "https://we.bitcoin-after.life" - } - }, - "2967d7c89ad9c988524978a11d5fa51aa16269049c57dce4d01baba990e2d95d": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, "UPDATED": false, "VALID": false, - "_id": "2967d7c89ad9c988524978a11d5fa51aa16269049c57dce4d01baba990e2d95d", - "baltx_fees": 100, + "_id": "11e2628fbdf54a7484b99124b2bc8f9ade3549d2eedcae72dd42cded7f9ac4eb", + "baltx_fees": 1, "change": null, - "description": "mario2\naaaa\nlucia\nmario", + "description": "w!ll3x3c\"http://localhost:9133\"1817352000\nmario2\naaaa\nlucia\nmario", "heirs": { "aaaa": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", "34%", - "300d", - 70460144114 + "1y", + 336619634 ], "lucia": [ "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", "32%", - "300d", - 66315429754 + "1y", + 316818479 ], "mario": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460144114 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Replaced.Invalidated", - "time": 1781960091.7051795, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff04214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc7a27b5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9df279c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcf279c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402204da23c14a00c8c13adbfafb9e8c709841a0dc0c6ae5a8f7e83d5b5d8765c73a002200c0488fd746623633072628b3d09e78b44ed845f4b50bacbe92c2ba3e2177cd2012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c09dbd6b", - "willexecutor": false - }, - "2ed95ab6bcf71b6fd9c6f81840654fb72f19ab945019f265253bf7414ec57496": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "2ed95ab6bcf71b6fd9c6f81840654fb72f19ab945019f265253bf7414ec57496", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1799208000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460129460 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "200d", - 66315415962 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460129460 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "200d", - 20001, - 20000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1799208000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1799208000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Updated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781964056.280334, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d9af1b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9db440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcb440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220340774663c70c09f523e56049fa09d189dea98faf701df8eaa59ae6b8144be8c022028e4546176a6445c5ed6b4573c78dd15aa7ac04c3a177e6c00f40b1821237147012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340bc3d6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d9af1b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9db440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcb440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220340774663c70c09f523e56049fa09d189dea98faf701df8eaa59ae6b8144be8c022028e4546176a6445c5ed6b4573c78dd15aa7ac04c3a177e6c00f40b1821237147012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340bc3d6b\n", - "txsids": [ - "2ed95ab6bcf71b6fd9c6f81840654fb72f19ab945019f265253bf7414ec57496" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "30c57bb2828d8fb85ec366a1642093318fd3d4c3e8cb69f90fa15ab3d25c633d": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": false, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "30c57bb2828d8fb85ec366a1642093318fd3d4c3e8cb69f90fa15ab3d25c633d", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460137950 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "120d", - 66315423952 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460137950 + "35%", + "1y", + 346520211 ], "mario2": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", 40000, - "120d", - 40001, + "1y", + 40002, 40000 + ], + "w!ll3x3c\"http://localhost:9133\"1817352000": [ + "bcrt1qrjwc0ww8xcvgum9zwmq858wndqguw7q27ek3sk", + 1000, + 1817352000, + 1000 ] }, "sigs_have": 0, - "sigs_required": 0, - "status": "New.Updated.Firmato.Invalidated", - "time": 1781968538.3178709, - "tx": "02000000000101d048cc969bc0a6ecea52609029b4c1c547afcf94ad0d7fb66166b15824e8fb3f0000000000fdffffff04419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcd010b5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dde61c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcde61c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022076dd8e78312b62c0daa890d62eb70add3e5a6542afb65695eaa1e0aeeb042dad0220127037d614d01fdd9e3bf21a049931b6f1113d4521b06b7357b978b507ee0515012102b72cc9ba68640a476abe173da069c5d37a1091346d9242fd794b470d04a236cd4044d46a", - "willexecutor": false - }, - "3318599c8ac525d9f9a29c298a943ae887afbf31ed92b781ceaa547e3ddd352b": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "3318599c8ac525d9f9a29c298a943ae887afbf31ed92b781ceaa547e3ddd352b", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460139660 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315425562 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460139660 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 10000, - 1807848000, - 10000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Updated.Replaced.Invalidated", - "time": 1781960130.4184484, - "tx": "cHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA", + "sigs_required": 1, + "status": "New.Signed.Pushed.Checked.Invalidated", + "time": 1785872109.7311695, + "tx": "02000000000101be5e0d37d1d9583a2c8e2dffd59192f663d85af9edfce4fcb2f129aea8be72660000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a429c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc2f44e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d72681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc937aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205e8a50ffca86b91b54455f9086b9a0bf81ec5443f88b396ecc72e6f0202f1c9b022072d8c01bbb872414df45895c9ded797ca9c79c9af11896e7f74fa7125bb2da55012102825bd3669bd57813180d972def557b09b204c631fed9654af4870c80130ddf184097526c", "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 10000, - "broadcast_status": "Waiting...", - "info": "Bitcoin After Life Will Executor", + "address": "bcrt1qrjwc0ww8xcvgum9zwmq858wndqguw7q27ek3sk", + "balance": 90000, + "base_fee": 1000, + "broadcast_status": "Success", + "chain": "regtest", + "count_win": 0, + "id": 66, + "info": "BAL devel willexecutor server", + "last_block": 0, + "last_update": 1785592563.693952, + "onion_url": null, + "points": 0, "promo_code": null, "selected": true, - "status": "New", - "txs": "cHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA\ncHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA\n020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05204e000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc9a0ab5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d445bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc445bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022036cb123a7272c69e1bb485912617d0ae587c01fbbbeba14a2d77803a6119ca5c022068db54ab23b601f494c31c6922fcda7b5074d7d20510c4a32f2acde3df8d5039012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b\n", + "status": 200, + "tld": "localhost", + "txs": "02000000000101be5e0d37d1d9583a2c8e2dffd59192f663d85af9edfce4fcb2f129aea8be72660000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a429c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc2f44e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d72681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc937aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205e8a50ffca86b91b54455f9086b9a0bf81ec5443f88b396ecc72e6f0202f1c9b022072d8c01bbb872414df45895c9ded797ca9c79c9af11896e7f74fa7125bb2da55012102825bd3669bd57813180d972def557b09b204c631fed9654af4870c80130ddf184097526c\n", "txsids": [ - "3318599c8ac525d9f9a29c298a943ae887afbf31ed92b781ceaa547e3ddd352b", - "b708916e8f8ab4562bb415f6876276e3901231649a9ed7f2d22ed0bae65cf0b9", - "04a14aa60b6ef0c727fae209070dcb7926389aea0ef643ceaaf788c2c6e8ea1f" + "11e2628fbdf54a7484b99124b2bc8f9ade3549d2eedcae72dd42cded7f9ac4eb" ], - "url": "https://we.bitcoin-after.life" + "unconfirmed_balance": 0, + "url": "http://localhost:9133", + "version": "0.3.2" } }, - "41e4db32c6457c93f853d452eed770ba23c78a3127f620fce080af0ddacec101": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "41e4db32c6457c93f853d452eed770ba23c78a3127f620fce080af0ddacec101", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460140714 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "200d", - 66315426554 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460140714 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 30000, - "200d", - 30001, - 30000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Updated.Firmato.Replaced.Invalidated", - "time": 1781964114.071311, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0431750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcfa1ab5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9daa6cc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcaa6cc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022059045766338bac0862f8b3d70fd5f33682ad976df5d390cb7d6d241bd8839fe002203742da94d9a7145737288303030e1197fe38629d56840c48c220ab2061f2dda2012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c06a3c6b", - "willexecutor": false - }, - "49d611bdf156bc19df59502a466a0c379f155d4c9d7509917faaf9f9ba67672b": { + "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8": { "ANTICIPATED": false, "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "49d611bdf156bc19df59502a466a0c379f155d4c9d7509917faaf9f9ba67672b", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460130470 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315416912 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460130470 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - "300d", - 40001, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Updated.Firmato.Replaced.Invalidated", - "time": 1781969519.6341054, - "tx": "020000000001018d2e8abfce92293e83c639dd0371e5fecefefb7b980ed54a2239a01624892e640000000000fdffffff04419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc50f5b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9da644c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca644c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022023cc71809c27b03f64d317dec85cb5be9291f12af999dbb33a7ca3271123e32b02200d78246d7fd23c857f408fc393f6e79a4459257ca601ada4630cac806e5bf49c012102ad2e7f599fec06e794dabe7d7c97100c8a16a5a8ee7a473e6b877629521ecae04092c16b", - "willexecutor": false - }, - "5e9355915428b150861ede28eb86cfd17692e063f495657c3fdedc267a95e20b": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "5e9355915428b150861ede28eb86cfd17692e063f495657c3fdedc267a95e20b", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460129460 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315415962 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460129460 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1807848000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781962665.8778772, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d9af1b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9db440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcb440c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402200e8ed269cd0443b9259ac666ca8a349f44aa4607a935fdf6f9bceebe1cc69bfd0220450cf86be10e9454abeb2815ed84960e1d45e5d489a43fe4c84bb2f943ae6975012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "url": "https://we.bitcoin-after.life" - } - }, - "5f8c84f762867a340f7be5577bdfe09fced800b44c5a03e92f4092440e9004f2": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "5f8c84f762867a340f7be5577bdfe09fced800b44c5a03e92f4092440e9004f2", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460147514 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315432954 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460147514 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 10000, - "300d", - 10001, - 10000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Updated.Replaced.Invalidated", - "time": 1781888681.1007006, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0411270000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcfa33b5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d3a87c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc3a87c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402207b3c0b9fb449501283229d37ab20fa8ecf1ba65835532ce570b62c98cee78ea002202813d74f280f9d04f90d91e87d97f772656fdcce20246f6fec59f7188e984aee012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340efbe6b", - "willexecutor": false - }, - "6e8b3178ff725013483725bd95df08f7a0284f6dc0a62ab8740d191a8c5889b2": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "6e8b3178ff725013483725bd95df08f7a0284f6dc0a62ab8740d191a8c5889b2", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460144114 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "200d", - 66315429754 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460144114 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "200d", - 20001, - 20000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Replaced.Invalidated", - "time": 1781964056.280334, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff04214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc7a27b5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9df279c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcf279c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220195ddae3a7de4959daf7d89105692600d1e0164ce0fa535251ee06b47adb11780220046455aedf9b97c4ea38abc52c35798e5b194a7d971547b79fab7e1cd7b174f0012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340bc3d6b", - "willexecutor": false - }, - "7a94407ccb2efc2445542bfe1107eadafb8da00692fb058c8ab3f8013ba7a013": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "7a94407ccb2efc2445542bfe1107eadafb8da00692fb058c8ab3f8013ba7a013", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460144114 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315429754 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460144114 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Updated.Firmato.Replaced.Invalidated", - "time": 1781960130.4184484, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff04214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc7a27b5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9df279c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcf279c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220281673a776da91290720d87a3c23756097c865fc9bf332f343ed52a9bf7adebe0220689a09e1968a213475f0f96214f0ca49a8b52d167c104a9c1a1db369ef458e41012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3404cbc6b", - "willexecutor": false - }, - "7be33b8465d0cc539990f55186d0a62c9a303b0f955dc2fc35a98da75749bb3d": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": false, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "7be33b8465d0cc539990f55186d0a62c9a303b0f955dc2fc35a98da75749bb3d", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460134210 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "200d", - 66315420432 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460134210 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - "200d", - 40001, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Updated.Firmato.Invalidated", - "time": 1781969271.933921, - "tx": "02000000000101609438ba82251a8cf3acfd1f189369029290cf2f8793b2b8f3c4c363c8e6c1a30000000000fdffffff04419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc1003b5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d4253c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc4253c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402202f3bb3b9a7b540b553cd3b78d8ece4b5aaf9d2c47fd0077540eea803a063306e02202b43c2812e9deef1e2446a3e1e55745282881410060c41ee3c8fb59937a08e66012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340bc3d6b", - "willexecutor": false - }, - "92687e2749995130bbb0a7e2e89961d3aca94131099585d0c4d1508ebfa27752": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "92687e2749995130bbb0a7e2e89961d3aca94131099585d0c4d1508ebfa27752", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460109060 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315396762 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460109060 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 100000, - 1807848000, - 100000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Updated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781960091.7051795, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d9aa6b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d04f1bf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc04f1bf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220646865bede38ec96e9c7c02666bdb5a8807db670703e2549a227f97ec900f5e8022001e255b37bc5a09d170f2c74487cf1952a9a3a38a709da2c741ebff2933a1ecf012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c09dbd6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 100000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05214e0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d9aa6b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d04f1bf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc04f1bf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220646865bede38ec96e9c7c02666bdb5a8807db670703e2549a227f97ec900f5e8022001e255b37bc5a09d170f2c74487cf1952a9a3a38a709da2c741ebff2933a1ecf012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c09dbd6b\n", - "txsids": [ - "92687e2749995130bbb0a7e2e89961d3aca94131099585d0c4d1508ebfa27752" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "934671a6ef20b76a3ea106e69ff79d62851c1213b3540bc6fb8c34a253b83dcc": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "934671a6ef20b76a3ea106e69ff79d62851c1213b3540bc6fb8c34a253b83dcc", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807761600\naaaa\nlucia\nmario\nmario2", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 58716763216 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 55262835968 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 58716763216 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "20%", - "300d", - 34539272480 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807761600": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 100000, - 1807761600, - 100000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Push failed.Replaced.Invalidated", - "time": 1781888638.550394, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05a086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d2079b30a080000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc00f5ebdd0c0000001600147e19af296f25d092f23a0c208823e65c81a2af9d50b4caab0d0000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc50b4caab0d0000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022036a3c5f2cb80a9118d1af8d9a25eb33092fff8e0e9ceda3055148366fc7aef8902206532d305fa20385516860c527b822e493aaac7930a33ec114639b7428ea5cfd4012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c040c06b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 100000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05a086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d2079b30a080000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc00f5ebdd0c0000001600147e19af296f25d092f23a0c208823e65c81a2af9d50b4caab0d0000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc50b4caab0d0000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022036a3c5f2cb80a9118d1af8d9a25eb33092fff8e0e9ceda3055148366fc7aef8902206532d305fa20385516860c527b822e493aaac7930a33ec114639b7428ea5cfd4012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c040c06b\n", - "txsids": [ - "934671a6ef20b76a3ea106e69ff79d62851c1213b3540bc6fb8c34a253b83dcc" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "959f3e6f135b585811e87165352bb97ae696309a0e9e4f6c93f63c85b5178d2b": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "959f3e6f135b585811e87165352bb97ae696309a0e9e4f6c93f63c85b5178d2b", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1792296000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460126060 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "120d", - 66315412762 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460126060 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 30000, - "120d", - 30001, - 30000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1792296000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1792296000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Updated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781968091.2853925, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0531750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ae5b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022043294051a4a7eed0875f3737f0f4c74b3679a203c40e6ef54d740b6ff48e0ba10220521c2ebf00cecc6c756f60352e9842d9de757895728f1ec5a90ffad3693f64d5012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b34044d46a", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0531750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ae5b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022043294051a4a7eed0875f3737f0f4c74b3679a203c40e6ef54d740b6ff48e0ba10220521c2ebf00cecc6c756f60352e9842d9de757895728f1ec5a90ffad3693f64d5012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b34044d46a\n", - "txsids": [ - "959f3e6f135b585811e87165352bb97ae696309a0e9e4f6c93f63c85b5178d2b" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "98b959e4921df6feab764eb7f41f5afe49e7cae5950a10decb6e3c1644d873f1": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "98b959e4921df6feab764eb7f41f5afe49e7cae5950a10decb6e3c1644d873f1", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1799208000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460126060 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "200d", - 66315412762 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460126060 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 30000, - "200d", - 30001, - 30000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1799208000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1799208000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781964114.071311, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0531750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ae5b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220233d22dabe66850fcd5cea525646981cc7f3e24bafb7a0ead451bcb6b8345f1c02203e82f071e534f9379f6fc893914ac8fc2a129848f8ea460c752e409e96b33c71012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c06a3c6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0531750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ae5b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220233d22dabe66850fcd5cea525646981cc7f3e24bafb7a0ead451bcb6b8345f1c02203e82f071e534f9379f6fc893914ac8fc2a129848f8ea460c752e409e96b33c71012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c06a3c6b\n", - "txsids": [ - "98b959e4921df6feab764eb7f41f5afe49e7cae5950a10decb6e3c1644d873f1" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "a38fdd27e8ab86430603726fb3d9c7c30e452df0aca433daf822bcecb99c9fbd": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "a38fdd27e8ab86430603726fb3d9c7c30e452df0aca433daf822bcecb99c9fbd", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807761600\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460112460 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315399962 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460112460 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 10000, - "300d", - 10001, - 10000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807761600": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 100000, - 1807761600, - 100000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781888681.1007006, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0511270000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ab3b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d4cfebf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc4cfebf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022043491efd8be6c3d2d7b37af6823e7d5c2e0d15086e01cd5fe36935fc119674080220652135dadf0d85ad5d647e32ad7dd7e039fd8f35ed03d5ae8ff8cd0cb56ac9e9012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340efbe6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 100000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0511270000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ab3b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d4cfebf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc4cfebf67100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022043491efd8be6c3d2d7b37af6823e7d5c2e0d15086e01cd5fe36935fc119674080220652135dadf0d85ad5d647e32ad7dd7e039fd8f35ed03d5ae8ff8cd0cb56ac9e9012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340efbe6b\n", - "txsids": [ - "a38fdd27e8ab86430603726fb3d9c7c30e452df0aca433daf822bcecb99c9fbd" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "a91ff4a30363a1907d57093a92adeaed86820cb36e6225b571b17b655e0bf512": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": true, - "VALID": false, - "_id": "a91ff4a30363a1907d57093a92adeaed86820cb36e6225b571b17b655e0bf512", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1794888000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "150d", - 70460126060 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "150d", - 66315412762 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "150d", - 70460126060 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 30000, - "150d", - 30001, - 30000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1794888000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1794888000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Updated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781967421.724049, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0531750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ae5b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220578abf0bcc0606c2ca89062daa5b59cf2a1adfb4732e166a0485c31c8b1b7e07022058e1f2baa2a4ccccfa460fb569c97df13930eb1d98a463eb8b9062e7b9989ba7012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340d1fb6a", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0531750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d1ae5b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6c33c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220578abf0bcc0606c2ca89062daa5b59cf2a1adfb4732e166a0485c31c8b1b7e07022058e1f2baa2a4ccccfa460fb569c97df13930eb1d98a463eb8b9062e7b9989ba7012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340d1fb6a\n", - "txsids": [ - "a91ff4a30363a1907d57093a92adeaed86820cb36e6225b571b17b655e0bf512" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, + "CHECKED": true, "CHECK_FAIL": false, "COMPLETE": true, "CONFIRMED": false, @@ -13145,53 +11902,53 @@ "RESTORED": false, "UPDATED": false, "VALID": true, - "_id": "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91", + "_id": "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8", "baltx_fees": 1, "change": null, - "description": "w!ll3x3c\"http://localhost:9133\"1817092800\nmario2\naaaa\nlucia\nmario", + "description": "w!ll3x3c\"http://localhost:9133\"1817438400\nmario2\naaaa\nlucia\nmario", "heirs": { "aaaa": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", "34%", "1y", - 336619671 + 336619597 ], "lucia": [ "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", "32%", "1y", - 316818514 + 316818444 ], "mario": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", "35%", "1y", - 346520250 + 346520173 ], "mario2": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", 40000, "1y", - 40001, + 40002, 40000 ], - "w!ll3x3c\"http://localhost:9133\"1817092800": [ + "w!ll3x3c\"http://localhost:9133\"1817438400": [ "bcrt1qrjwc0ww8xcvgum9zwmq858wndqguw7q27ek3sk", 1000, - 1817092800, + 1817438400, 1000 ] }, - "sigs_have": 0, + "sigs_have": 1, "sigs_required": 1, - "status": "New.Firmato.Pushed", - "time": 1785620332.2813325, - "tx": "020000000001014c4fffdabe4d27a8b06618492a5fe828345551b3da8107b8cada5f452995652c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc5244e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d97681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcba7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205b777b06e8392136b30177d8a353bde7238a5aa5235eaf973a298c0eb974b4750220783b0b1c7093c93a2aee94829c6e0f6288cecd9e8eba94c61b9c54c2c84f4d2c0121032592689364f3a4796fc35a1f183791eb7c5db25e6f36fa0f1e92654ed679c791c0a24e6c", + "status": "New.Signed.Pushed.Checked", + "time": 1785962732.933536, + "tx": "020000000001012643fd84307dc1b26b2cbb719d82112953e46de3cce36eaca7d32903cf79e5270000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a429c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0c44e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d4d681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6d7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022043be5753ad8ebcacf8e37c55fe0e59b04b6012892b149c63dd9d91b0f97096710220700565cd7bd947469c29cafb5c3fa80a090c31d920f26ec28483536f1531c8a6012102321c23031f88968710649e9ed043a0b484a5db5febb565152e15ccd288ece7f3c0e8536c", "willexecutor": { "address": "bcrt1qrjwc0ww8xcvgum9zwmq858wndqguw7q27ek3sk", "balance": 90000, "base_fee": 1000, - "broadcast_status": "Riuscito", + "broadcast_status": "Waiting...", "chain": "regtest", "count_win": 0, "id": 66, @@ -13204,668 +11961,14 @@ "selected": true, "status": 200, "tld": "localhost", - "txs": "020000000001014c4fffdabe4d27a8b06618492a5fe828345551b3da8107b8cada5f452995652c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc5244e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d97681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcba7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205b777b06e8392136b30177d8a353bde7238a5aa5235eaf973a298c0eb974b4750220783b0b1c7093c93a2aee94829c6e0f6288cecd9e8eba94c61b9c54c2c84f4d2c0121032592689364f3a4796fc35a1f183791eb7c5db25e6f36fa0f1e92654ed679c791c0a24e6c\n", + "txs": "020000000001012643fd84307dc1b26b2cbb719d82112953e46de3cce36eaca7d32903cf79e5270000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a429c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0c44e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d4d681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc6d7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc02473044022043be5753ad8ebcacf8e37c55fe0e59b04b6012892b149c63dd9d91b0f97096710220700565cd7bd947469c29cafb5c3fa80a090c31d920f26ec28483536f1531c8a6012102321c23031f88968710649e9ed043a0b484a5db5febb565152e15ccd288ece7f3c0e8536c\n", "txsids": [ - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91" + "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8" ], "unconfirmed_balance": 0, "url": "http://localhost:9133", "version": "0.3.2" } - }, - "b708916e8f8ab4562bb415f6876276e3901231649a9ed7f2d22ed0bae65cf0b9": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "3318599c8ac525d9f9a29c298a943ae887afbf31ed92b781ceaa547e3ddd352b", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460139660 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315425562 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460139660 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 20000, - "300d", - 20001, - 20000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 10000, - 1807848000, - 10000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Replaced.Invalidated", - "time": 1781960130.4184484, - "tx": "cHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 10000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "cHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA\ncHNidP8BAM4CAAAAATwyIk7fo395BQp+yWQkAi3cbJvLbRmda5/tvOr9l4OrAAAAAAD9////BRAnAAAAAAAAFgAU7TE18rdHWuOJsZ6gBUxSJGsDNW0hTgAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8Ghe1cA8AAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnYxowGcQAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7yMaMBnEAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8QEy8awABAR/LuDZAMAAAABYAFGBQRfMEYDJh1QX/1tBRR+VvkLw1AQC/AgAAAAABAdBIzJabwKbs6lJgkCm0wcVHr8+UrQ1/tmFmsVgk6Ps/AAAAAAD9////Acu4NkAwAAAAFgAUYFBF8wRgMmHVBf/W0FFH5W+QvDUCRzBEAiBsSSpHFP2Gh940TxVArbX+kwXOWOx+h6QMC6uGd1BJ2AIgRAA9Mt59IRdg35PVtTzAiXxpRBbNtC5iFkrG8a3JpwkBIQK3LMm6aGQKR2q+Fz2gacXTehCRNG2SQv15S0cNBKI2zd0FAAAiBgPqVPo0IcmIzsGP4vghpv7ElJA+HX0ubSzmFtmxSKqQsxBZSzQGAAAAgAEAAAALAAAAAAAAAAAA\n", - "txsids": [ - "b708916e8f8ab4562bb415f6876276e3901231649a9ed7f2d22ed0bae65cf0b9", - "3318599c8ac525d9f9a29c298a943ae887afbf31ed92b781ceaa547e3ddd352b" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "cc249f4b68e8d52c4a2622d86bf13ebc256525e2e85dd7dd02514658e869d4dd": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "cc249f4b68e8d52c4a2622d86bf13ebc256525e2e85dd7dd02514658e869d4dd", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460140714 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "120d", - 66315426554 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460140714 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 30000, - "120d", - 30001, - 30000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Replaced.Invalidated", - "time": 1781968091.2853925, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0431750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcfa1ab5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9daa6cc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcaa6cc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402207c5a4fe19fc15204abc89058ce988eeb499d52b906a51f6347d69581903b2b0c022001c7b4396bcd9b8ff0bd13bd9014468d75fd140a19703c0876f91912b9596104012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b34044d46a", - "willexecutor": false - }, - "d3c6048e8b2d169ea88a8b85869e996b165181a8532853c1e94eb6010a53164f": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "d3c6048e8b2d169ea88a8b85869e996b165181a8532853c1e94eb6010a53164f", - "baltx_fees": 100, - "change": null, - "description": "mario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "150d", - 70460140714 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "150d", - 66315426554 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "150d", - 70460140714 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 30000, - "150d", - 30001, - 30000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Replaced.Invalidated", - "time": 1781967421.724049, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff0431750000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcfa1ab5700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9daa6cc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcaa6cc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402207dc67b956e892ae5a44a0d015501f8e0f39f6a6987e750a77ee6bc73a6fc3d7f02205d358971ecf087d7d9e8c424f63e3c5fa0c17b52b31b88a243d4aa9d8cd2f1f8012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340d1fb6a", - "willexecutor": false - }, - "dabe77c276942c463e0c34b49b1aba08f563dce97a32c41c098d59e58f476539": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": false, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "dabe77c276942c463e0c34b49b1aba08f563dce97a32c41c098d59e58f476539", - "baltx_fees": 1, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1814068800\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - 1814068800.0, - 69762478507 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - 1814068800.0, - 65658803301 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "35%", - 1814068800.0, - 71814316110 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - 1814068800.0, - 40001, - 40000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1814068800": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 100000, - 1814068800, - 100000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Invalidated", - "time": 1782556752.5544994, - "tx": "020000000001018d2e8abfce92293e83c639dd0371e5fecefefb7b980ed54a2239a01624892e640000000000fdffffff05419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d65d491490f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dabf12a3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc4e8077b8100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220459f471c2befd1bd528e5059675f12aba5d2117d8d3bd5005aca401cab307aa002201e50c4b3da8f15c62e33f9fd9cd99645046b4eb0348d77beeeac6c1f929a3568012102ad2e7f599fec06e794dabe7d7c97100c8a16a5a8ee7a473e6b877629521ecae0c040c06b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 100000, - "broadcast_status": "Waiting...", - "info": "Bitcoin After Life Will Executor", - "last_update": 1782551347.567278, - "promo_code": null, - "selected": true, - "status": "KO", - "txs": "020000000001018d2e8abfce92293e83c639dd0371e5fecefefb7b980ed54a2239a01624892e640000000000fdffffff05419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d65d491490f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dabf12a3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc4e8077b8100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220459f471c2befd1bd528e5059675f12aba5d2117d8d3bd5005aca401cab307aa002201e50c4b3da8f15c62e33f9fd9cd99645046b4eb0348d77beeeac6c1f929a3568012102ad2e7f599fec06e794dabe7d7c97100c8a16a5a8ee7a473e6b877629521ecae0c040c06b\n020000000001018d2e8abfce92293e83c639dd0371e5fecefefb7b980ed54a2239a01624892e640000000000fdffffff05419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d65d491490f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dabf12a3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc4e8077b8100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220381a8ccc9edba0d8b8d304493103639e58a4c19d79525976ab09f990f8c104440220720b86e4c80b61068195dd62f0e28d4cd11793b2683acacfeb2b507e5b7fa360012102ad2e7f599fec06e794dabe7d7c97100c8a16a5a8ee7a473e6b877629521ecae0407e206c\n", - "txsids": [ - "dabe77c276942c463e0c34b49b1aba08f563dce97a32c41c098d59e58f476539", - "e289ca8134bc76e77dfe30295482489bfe15831bf6d6326471fb8d1fc876ca96" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "e289ca8134bc76e77dfe30295482489bfe15831bf6d6326471fb8d1fc876ca96": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": false, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "e289ca8134bc76e77dfe30295482489bfe15831bf6d6326471fb8d1fc876ca96", - "baltx_fees": 1, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1814068800\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - 1814068800.0, - 69762478507 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - 1814068800.0, - 65658803301 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "35%", - 1814068800.0, - 71814316110 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - 1814068800.0, - 40001, - 40000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1814068800": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 100000, - 1814068800, - 100000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Replaced.Invalidated", - "time": 1782556767.004284, - "tx": "020000000001018d2e8abfce92293e83c639dd0371e5fecefefb7b980ed54a2239a01624892e640000000000fdffffff05419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca086010000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d65d491490f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dabf12a3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc4e8077b8100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220381a8ccc9edba0d8b8d304493103639e58a4c19d79525976ab09f990f8c104440220720b86e4c80b61068195dd62f0e28d4cd11793b2683acacfeb2b507e5b7fa360012102ad2e7f599fec06e794dabe7d7c97100c8a16a5a8ee7a473e6b877629521ecae0407e206c", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 100000, - "info": "Bitcoin After Life Will Executor", - "last_update": 1782551347.567278, - "promo_code": null, - "selected": true, - "status": "KO", - "url": "https://we.bitcoin-after.life" - } - }, - "eb9f798774b196368f5f1e24a22669c48dcd8406aee7b8de878ad0a4b42a189f": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": false, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "eb9f798774b196368f5f1e24a22669c48dcd8406aee7b8de878ad0a4b42a189f", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1792296000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460123296 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "120d", - 66315410160 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460123296 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - "120d", - 40001, - 40000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1792296000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1792296000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Push failed.Invalidated", - "time": 1781968538.3178709, - "tx": "02000000000101d048cc969bc0a6ecea52609029b4c1c547afcf94ad0d7fb66166b15824e8fb3f0000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcf0dab4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9da028c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca028c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205df4e886280853547a595e4aebf4bc04fe71c6b0e5b81f765492637183e3e5b902202deae6e1b3707f4a9242638509af4411f1347bf6e2c8c8f02308880f338f9c09012102b72cc9ba68640a476abe173da069c5d37a1091346d9242fd794b470d04a236cd4044d46a", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "02000000000101d048cc969bc0a6ecea52609029b4c1c547afcf94ad0d7fb66166b15824e8fb3f0000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcf0dab4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9da028c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbca028c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205df4e886280853547a595e4aebf4bc04fe71c6b0e5b81f765492637183e3e5b902202deae6e1b3707f4a9242638509af4411f1347bf6e2c8c8f02308880f338f9c09012102b72cc9ba68640a476abe173da069c5d37a1091346d9242fd794b470d04a236cd4044d46a\n", - "txsids": [ - "eb9f798774b196368f5f1e24a22669c48dcd8406aee7b8de878ad0a4b42a189f" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "eced91d575c32dbf7b9712d7f2e6881f5992cdad954d486f4278f223cc7cae02": { - "ANTICIPATED": true, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": false, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "eced91d575c32dbf7b9712d7f2e6881f5992cdad954d486f4278f223cc7cae02", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1792296000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460122660 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "120d", - 66315409562 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "120d", - 70460122660 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - "120d", - 40001, - 40000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1792296000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1792296000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Anticipated.Firmato.Push failed.Invalidated", - "time": 1781968114.0018213, - "tx": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc9ad8b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d2426c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc2426c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220046f713d5ff3427a3839a54fad776840265d32a008c0132dd067eb925b42d9a30220386a1bd590869e913898856c81df9a1aa1e2ea1fb11f76d418324b3f6ba770b2012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c0f2d26a", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001013c32224edfa37f79050a7ec96424022ddc6c9bcb6d199d6b9fedbceafd9783ab0000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc9ad8b4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d2426c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc2426c067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc024730440220046f713d5ff3427a3839a54fad776840265d32a008c0132dd067eb925b42d9a30220386a1bd590869e913898856c81df9a1aa1e2ea1fb11f76d418324b3f6ba770b2012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b3c0f2d26a\n", - "txsids": [ - "eced91d575c32dbf7b9712d7f2e6881f5992cdad954d486f4278f223cc7cae02" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "edf4cf16e67adb2b4edae518122f902d5e6c07499ea4f5661e32b272bb8d67d0": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": false, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "edf4cf16e67adb2b4edae518122f902d5e6c07499ea4f5661e32b272bb8d67d0", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1799208000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460119556 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "200d", - 66315406640 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "200d", - 70460119556 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - "200d", - 40001, - 40000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1799208000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1799208000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Push failed.Invalidated", - "time": 1781969271.933921, - "tx": "02000000000101609438ba82251a8cf3acfd1f189369029290cf2f8793b2b8f3c4c363c8e6c1a30000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc30cdb4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d041ac067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc041ac067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402207723e0a35a030b4032c5961482c14dfa4bc6c7c09e71d83e5b487742825100eb02205f3f81b10a592fc367b8bf3ed055d4d8849413c8d4c16cd89a555f5186bd9fbc012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340bc3d6b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "02000000000101609438ba82251a8cf3acfd1f189369029290cf2f8793b2b8f3c4c363c8e6c1a30000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc30cdb4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d041ac067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc041ac067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402207723e0a35a030b4032c5961482c14dfa4bc6c7c09e71d83e5b487742825100eb02205f3f81b10a592fc367b8bf3ed055d4d8849413c8d4c16cd89a555f5186bd9fbc012103ea54fa3421c988cec18fe2f821a6fec494903e1d7d2e6d2ce616d9b148aa90b340bc3d6b\n", - "txsids": [ - "edf4cf16e67adb2b4edae518122f902d5e6c07499ea4f5661e32b272bb8d67d0" - ], - "url": "https://we.bitcoin-after.life" - } - }, - "f3fe529f7269138b4d08bb934e0d61546d5d590db7a3f62a23a360590a3d533b": { - "ANTICIPATED": false, - "BROADCASTED": false, - "CHECKED": false, - "CHECK_FAIL": false, - "COMPLETE": true, - "CONFIRMED": false, - "ERROR": false, - "EXPIRED": false, - "EXPORTED": false, - "IMPORTED": false, - "INVALIDATED": true, - "MEMPOOL": false, - "PARTIALLY_SIGNED": false, - "PUSHED": false, - "PUSH_FAIL": true, - "REPLACED": true, - "RESTORED": false, - "UPDATED": false, - "VALID": false, - "_id": "f3fe529f7269138b4d08bb934e0d61546d5d590db7a3f62a23a360590a3d533b", - "baltx_fees": 100, - "change": null, - "description": "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000\nmario2\naaaa\nlucia\nmario", - "heirs": { - "aaaa": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460115816 - ], - "lucia": [ - "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", - "32%", - "300d", - 66315403120 - ], - "mario": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - "34%", - "300d", - 70460115816 - ], - "mario2": [ - "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", - 40000, - "300d", - 40001, - 40000 - ], - "w!ll3x3c\"https://we.bitcoin-after.life\"1807848000": [ - "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - 40000, - 1807848000, - 40000 - ] - }, - "sigs_have": 0, - "sigs_required": 0, - "status": "New.Firmato.Replaced.Push failed.Invalidated", - "time": 1781969519.6341054, - "tx": "020000000001018d2e8abfce92293e83c639dd0371e5fecefefb7b980ed54a2239a01624892e640000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc70bfb4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d680bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc680bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402206fc53acf1f67198ce0b36e5624e8d509f301ab4729e9e0776b59ea7afce75322022050a99933c34b6189266b4e234b48368fe9178caf915c63f21aa720fe5b7798bf012102ad2e7f599fec06e794dabe7d7c97100c8a16a5a8ee7a473e6b877629521ecae04092c16b", - "willexecutor": { - "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", - "base_fee": 40000, - "broadcast_status": "Fallito", - "info": "Bitcoin After Life Will Executor", - "promo_code": null, - "selected": true, - "status": "New", - "txs": "020000000001018d2e8abfce92293e83c639dd0371e5fecefefb7b980ed54a2239a01624892e640000000000fdffffff05409c000000000000160014ed3135f2b7475ae389b19ea0054c52246b03356d419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc70bfb4700f0000001600147e19af296f25d092f23a0c208823e65c81a2af9d680bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc680bc067100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402206fc53acf1f67198ce0b36e5624e8d509f301ab4729e9e0776b59ea7afce75322022050a99933c34b6189266b4e234b48368fe9178caf915c63f21aa720fe5b7798bf012102ad2e7f599fec06e794dabe7d7c97100c8a16a5a8ee7a473e6b877629521ecae04092c16b\n", - "txsids": [ - "f3fe529f7269138b4d08bb934e0d61546d5d590db7a3f62a23a360590a3d533b" - ], - "url": "https://we.bitcoin-after.life" - } } }, "will_settings": { diff --git a/tests/test_core_checkalive.py b/tests/test_core_checkalive.py new file mode 100644 index 0000000..9f5960a --- /dev/null +++ b/tests/test_core_checkalive.py @@ -0,0 +1,95 @@ +""" +Tests for ``bal.core.checkalive`` (pure, GUI-free). + +Covers the CheckAliveError exception and the BASIC/ADVANCED date_to_check +policy (``resolve_date_to_check`` / ``check_alive_expired``). + +Run: + source /home/steal/devel/bal/electrum/env/bin/activate + python3 tests/test_core_checkalive.py +""" + +import sys +import time + +sys.path.insert(0, __file__.rsplit("/", 2)[0]) + +from bal.core.checkalive import ( # noqa: E402 (path insert above) + CheckAliveError, + check_alive_expired, + resolve_date_to_check, +) + +# ------------------------------------------------------------------ # +# CheckAliveError +# ------------------------------------------------------------------ # + + +def test_check_alive_error_default(): + err = CheckAliveError(1000000) + assert err.timestamp_to_check == 1000000 + + +def test_check_alive_error_str(): + err = CheckAliveError(1000000) + s = str(err) + assert "Check alive expired" in s + assert "1970" in s + + +def test_check_alive_error_subclass(): + assert issubclass(CheckAliveError, Exception) + + +# ------------------------------------------------------------------ # +# resolve_date_to_check +# ------------------------------------------------------------------ # + + +def test_basic_mode_uses_now(): + fake_now = 1_800_000_000.0 + assert resolve_date_to_check(True, {}, now=fake_now) == fake_now + + +def test_advanced_mode_uses_threshold_absolute(): + threshold = time.time() + 5 * 86400 + settings = {"threshold": threshold} + result = resolve_date_to_check(False, settings) + assert abs(result - threshold) < 1 + + +def test_advanced_mode_parses_relative_threshold(): + # "30d" resolves to a future timestamp (midnight-normalised). + settings = {"threshold": "30d"} + result = resolve_date_to_check(False, settings) + assert result > time.time() + + +# ------------------------------------------------------------------ # +# check_alive_expired +# ------------------------------------------------------------------ # + + +def test_basic_mode_never_expired(): + assert check_alive_expired(True, 1_000_000_000.0) is False + assert check_alive_expired(True, time.time() - 10_000) is False + + +def test_advanced_expired_when_past(): + assert check_alive_expired(False, time.time() - 10_000) is True + + +def test_advanced_not_expired_when_future(): + assert check_alive_expired(False, time.time() + 10_000) is False + + +# ------------------------------------------------------------------ # +# Main +# ------------------------------------------------------------------ # + +if __name__ == "__main__": + for name in sorted(dir()): + if name.startswith("test_"): + globals()[name]() + print(f" [OK] {name}") + print("[OK] All core checkalive tests passed") diff --git a/tests/test_core_input_rules.py b/tests/test_core_input_rules.py new file mode 100644 index 0000000..b83bb68 --- /dev/null +++ b/tests/test_core_input_rules.py @@ -0,0 +1,165 @@ +""" +Tests for ``bal.core.input_rules`` (pure, GUI-free). + +Covers the locktime acceptance bounds, the RAW locktime sanitisation, and the +percentage-or-amount field normalisation that the Qt editors wrap. + +Run: + source /home/steal/devel/bal/electrum/env/bin/activate + python3 tests/test_core_input_rules.py +""" + +import sys + +sys.path.insert(0, __file__.rsplit("/", 2)[0]) + +from bal.core.input_rules import ( # noqa: E402 (path insert above) + LockTimeEditor, + normalize_locktime_raw_text, + normalize_perc_amount_text, + parse_perc_amount, + replace_dy_suffixes, +) + +# ------------------------------------------------------------------ # +# LockTimeEditor +# ------------------------------------------------------------------ # + + +def test_locktime_editor_is_acceptable(): + assert LockTimeEditor.is_acceptable_locktime(100) is True + assert LockTimeEditor.is_acceptable_locktime(0) is True + assert LockTimeEditor.is_acceptable_locktime(-1) is False + assert LockTimeEditor.is_acceptable_locktime(None) is True + + +def test_locktime_editor_is_acceptable_string(): + assert LockTimeEditor.is_acceptable_locktime("100") is True + assert LockTimeEditor.is_acceptable_locktime("abc") is False + assert LockTimeEditor.is_acceptable_locktime("") is True + + +def test_locktime_editor_min_max(): + assert LockTimeEditor.min_allowed_value >= 0 + assert LockTimeEditor.max_allowed_value > LockTimeEditor.min_allowed_value + + +def test_locktime_editor_get_max_allowed_timestamp(): + # Always a valid, fromtimestamp-able value (the Windows 2038 clamp). + ts = LockTimeEditor.get_max_allowed_timestamp() + import datetime + + datetime.datetime.fromtimestamp(ts) # must not raise + assert ts <= 2**32 - 1 + + +def test_locktime_editor_subclass_bounds(): + class Tight(LockTimeEditor): + min_allowed_value = 1000 + max_allowed_value = 2000 + + assert Tight.is_acceptable_locktime(1500) is True + assert Tight.is_acceptable_locktime(999) is False + assert Tight.is_acceptable_locktime(2001) is False + + +# ------------------------------------------------------------------ # +# replace_dy_suffixes / RAW locktime sanitisation +# ------------------------------------------------------------------ # + + +def test_replace_dy_suffixes(): + # replace_str only strips the day ("d") and year ("y") suffixes. The + # block-height suffix ("b") was removed (A1), so "b" is NOT stripped + # anymore (locktimes are always UNIX timestamps now). + assert replace_dy_suffixes("123d") == "123" + assert replace_dy_suffixes("456y") == "456" + # "b" is left untouched (no longer a recognised suffix) + assert replace_dy_suffixes("789b") == "789b" + # only d/y are stripped; a stray "b" remains + assert replace_dy_suffixes("12d34y56b") == "123456b" + + +def test_normalize_locktime_raw_empty(): + s, isdays, isyears, pos = normalize_locktime_raw_text("", 0) + assert s == "" + assert isdays is False + assert isyears is False + + +def test_normalize_locktime_raw_days(): + s, isdays, isyears, pos = normalize_locktime_raw_text("30d", 3) + assert s == "30d" + assert isdays is True + assert isyears is False + + +def test_normalize_locktime_raw_years(): + s, isdays, isyears, pos = normalize_locktime_raw_text("2y", 2) + assert s == "2y" + assert isdays is False + assert isyears is True + + +def test_normalize_locktime_raw_strips_bad_chars(): + s, isdays, isyears, pos = normalize_locktime_raw_text("12a34b56", 8) + assert s == "123456" + assert isdays is False + assert isyears is False + + +def test_normalize_locktime_raw_single_suffix(): + # "1y30d" collapses to a single suffix; "d" wins (processed first), so the + # "y" is dropped along with all letters. + s, isdays, isyears, pos = normalize_locktime_raw_text("1y30d", 5) + assert isdays is True + assert isyears is False + assert s == "130d" + + +# ------------------------------------------------------------------ # +# Percentage-or-amount normalisation +# ------------------------------------------------------------------ # + + +def test_perc_normalize_percent(): + s, is_perc = normalize_perc_amount_text("50%", ".") + assert is_perc is True + assert s == "50%" + + +def test_perc_normalize_no_percent(): + s, is_perc = normalize_perc_amount_text("123", ".") + assert is_perc is False + assert s == "123" + + +def test_perc_normalize_strips_invalid(): + s, is_perc = normalize_perc_amount_text("1a2b3", ".") + assert s == "123" + + +def test_perc_normalize_decimal_limit(): + # At most 8 decimals after the point. + s, is_perc = normalize_perc_amount_text("1.1234567890123", ".") + assert s == "1.12345678" + + +def test_perc_parse(): + from decimal import Decimal + + assert parse_perc_amount("50%", ".") == Decimal(50) + assert parse_perc_amount("123.45", ".") == Decimal("123.45") + assert parse_perc_amount("abc", ".") is None + + +# ------------------------------------------------------------------ # +# Main +# ------------------------------------------------------------------ # + +if __name__ == "__main__": + for name in sorted(dir()): + if name.startswith("test_"): + globals()[name]() + print(f" [OK] {name}") + print("[OK] All core input_rules tests passed") diff --git a/tests/test_core_reminders.py b/tests/test_core_reminders.py new file mode 100644 index 0000000..4ed2223 --- /dev/null +++ b/tests/test_core_reminders.py @@ -0,0 +1,299 @@ +""" +Tests for ``bal.core.reminders`` (pure, GUI-free). + +Covers the reminder-offset rules (BASIC + ADVANCED), the RFC-5545 helpers +(format_time, ical_escape, fold_ical_line), write_temp_ics, and the unified +``build_ics_reminders`` builder. + +This module imports only ``bal.core``, so it runs in the lint venv (no +Electrum/Qt needed) as well as the runtime venv: + +Run: + python3 tests/test_core_reminders.py +""" + +import os +import sys +from datetime import datetime, timedelta, timezone + +sys.path.insert(0, __file__.rsplit("/", 2)[0]) + +from bal.core.reminders import ( # noqa: E402 (path insert above) + BASIC_REMINDER_OFFSETS, + basic_reminder_offsets, + build_ics_reminders, + compute_reminder_offsets, + fold_ical_line, + format_time, + ical_escape, + write_temp_ics, +) + +# ------------------------------------------------------------------ # +# compute_reminder_offsets - distribution rules +# ------------------------------------------------------------------ # + + +def test_offsets_long_period_three_reminders(): + """30-day period, 3 reminders: spread out, all before the deadline.""" + offsets = compute_reminder_offsets(30, 3) + assert len(offsets) == 3 + # All strictly before the deadline (offset >= 1 means "n days before end"). + assert all(o >= 1 for o in offsets) + # Sorted earliest-first (largest offset first). + assert offsets == sorted(offsets, reverse=True) + # One reminder near the start, one near the end. + assert max(offsets) == 30 + assert min(offsets) == 1 + + +def test_offsets_capped_at_one_per_day(): + """Short period: at most one reminder per available day.""" + # 2 days but 3 requested -> only 2 reminders, one per day. + assert compute_reminder_offsets(2, 3) == [2, 1] + # 1 day but 3 requested -> a single reminder, the day before the deadline. + assert compute_reminder_offsets(1, 3) == [1] + + +def test_offsets_empty_when_no_room(): + """No reminders when there is no day before the deadline.""" + assert compute_reminder_offsets(0, 3) == [] + assert compute_reminder_offsets(-5, 3) == [] + # A non-positive count also yields nothing. + assert compute_reminder_offsets(30, 0) == [] + + +def test_offsets_never_exceed_requested_count(): + """The number of reminders never exceeds the requested count (max 5).""" + offsets = compute_reminder_offsets(100, 5) + assert len(offsets) == 5 + assert all(o >= 1 for o in offsets) + # Distinct offsets only (no duplicate alarms on the same day). + assert len(set(offsets)) == len(offsets) + + +def test_offsets_single_reminder_is_day_before_deadline(): + """A single requested reminder fires one day before the deadline.""" + assert compute_reminder_offsets(30, 1) == [1] + + +# ------------------------------------------------------------------ # +# basic_reminder_offsets - fixed BASIC-mode offsets +# ------------------------------------------------------------------ # + + +def test_basic_offsets_full_period(): + """A far-away delivery keeps all three fixed offsets (30, 10, 1).""" + assert basic_reminder_offsets(365) == [30, 10, 1] + + +def test_basic_offsets_truncated_by_horizon(): + """Offsets beyond the delivery horizon are dropped.""" + assert basic_reminder_offsets(20) == [10, 1] + assert basic_reminder_offsets(5) == [1] + + +def test_basic_offsets_empty(): + """No future offsets when the delivery is less than a day away.""" + assert basic_reminder_offsets(0) == [] + assert basic_reminder_offsets(-10) == [] + + +def test_basic_offsets_always_from_fixed_set(): + """Every returned offset is one of the fixed BASIC offsets.""" + for horizon in range(0, 40): + result = basic_reminder_offsets(horizon) + assert set(result).issubset(set(BASIC_REMINDER_OFFSETS)) + + +# ------------------------------------------------------------------ # +# format_time +# ------------------------------------------------------------------ # + + +def test_format_time_utc(): + dt = datetime(2025, 6, 1, 12, 30, 45, tzinfo=timezone.utc) + assert format_time(dt) == "20250601T123045Z" + + +def test_format_time_non_utc(): + tz = timezone(timedelta(hours=2)) + dt = datetime(2025, 6, 1, 12, 30, 45, tzinfo=tz) + assert format_time(dt) == "20250601T103045Z" + + +# ------------------------------------------------------------------ # +# ical_escape +# ------------------------------------------------------------------ # + + +def test_ical_escape_no_change(): + assert ical_escape("hello world") == "hello world" + + +def test_ical_escape_backslash(): + assert ical_escape("a\\b") == "a\\\\b" + + +def test_ical_escape_semicolon(): + assert ical_escape("a;b") == "a\\;b" + + +def test_ical_escape_comma(): + assert ical_escape("a,b") == "a\\,b" + + +def test_ical_escape_multiline(): + text = "line1\nline2" + result = ical_escape(text) + assert "line1" in result + assert "line2" in result + + +def test_ical_escape_all(): + assert ical_escape("\\;,") == "\\\\\\;\\," + + +# ------------------------------------------------------------------ # +# fold_ical_line +# ------------------------------------------------------------------ # + + +def test_fold_ical_line_short(): + assert fold_ical_line("SUMMARY:Test") == "SUMMARY:Test" + + +def test_fold_ical_line_long(): + line = "SUMMARY:" + "a" * 100 + result = fold_ical_line(line, limit=75) + parts = result.split("\r\n ") + assert all(len(p.encode("utf-8")) <= 75 for p in parts) + assert "".join(parts) == line + + +def test_fold_ical_line_unicode(): + line = "SUMMARY:" + "é" * 50 + result = fold_ical_line(line, limit=75) + parts = result.split("\r\n ") + assert all(len(p.encode("utf-8")) <= 75 for p in parts) + assert "".join(parts) == line + + +# ------------------------------------------------------------------ # +# write_temp_ics +# ------------------------------------------------------------------ # + + +def test_write_temp_ics(): + content = "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n" + path = write_temp_ics(content) + try: + assert os.path.isfile(path) + with open(path, "rb") as f: + assert f.read() == content.encode("utf-8") + finally: + os.unlink(path) + + +def test_write_temp_ics_empty(): + path = write_temp_ics("") + try: + assert os.path.isfile(path) + with open(path, "rb") as f: + assert f.read() == b"" + finally: + os.unlink(path) + + +# ------------------------------------------------------------------ # +# build_ics_reminders - unified builder +# ------------------------------------------------------------------ # + +_LOCKTIME = datetime(2026, 7, 23, 9, 0, 0, tzinfo=timezone.utc) + + +def _common_kwargs(): + return dict( + locktime=_LOCKTIME, + description="BAL will of $wallet_name\r\n$heirs_complete", + summary="BAL - Will execution of $wallet_name", + wallet_name="karen7", + heirs_details=" alice - bc1qalice, bob - bc1qbob", + version="0.6.1", + ) + + +def test_build_ics_advanced_separate_events(): + """ADVANCED: 30-day period -> offsets [30, 16, 1], one VEVENT each.""" + content = build_ics_reminders( + basic_mode=False, + num_reminders=3, + now=datetime(2026, 7, 1, 0, 0, 0, tzinfo=timezone.utc), + threshold=datetime(2026, 6, 23, 0, 0, 0, tzinfo=timezone.utc), + **_common_kwargs(), + ) + assert content is not None + assert content.startswith("BEGIN:VCALENDAR") + assert content.endswith("\r\n") + lines = content.split("\r\n") + assert lines.count("BEGIN:VEVENT") == 3 + assert lines.count("END:VEVENT") == 3 + assert "BEGIN:VALARM" not in lines + # Unique UIDs, numbered summaries, last event one day before the deadline. + uids = [ln for ln in lines if ln.startswith("UID:")] + assert len(uids) == len(set(uids)) == 3 + assert any("(reminder 1/3)" in ln for ln in lines) + assert any("(reminder 3/3)" in ln for ln in lines) + last_dt = format_time(_LOCKTIME - timedelta(days=1)) + assert f"DTSTART:{last_dt}" in lines + # Template substitution; the CRLF inside the description stays as a line + # break in the folded DESCRIPTION (no literal "\n" escaping in ical_escape). + desc = [ln for ln in lines if ln.startswith("DESCRIPTION:")] + assert desc and "karen7" in desc[0] + assert any(ln.lstrip().startswith("alice") for ln in lines) + +def test_build_ics_basic_uses_fixed_offsets(): + """BASIC: fixed offsets (30, 10, 1) filtered to the future, threshold not + required.""" + content = build_ics_reminders( + basic_mode=True, + now=datetime(2026, 6, 1, 0, 0, 0, tzinfo=timezone.utc), + **_common_kwargs(), + ) + assert content is not None + lines = content.split("\r\n") + uids = [ln for ln in lines if ln.startswith("UID:")] + assert len(uids) == 3 + # The first event is the 30-day offset. + assert "bal-karen7-30d" in uids[0] + + +def test_build_ics_returns_none_when_no_reminders(): + """No future reminder -> None (the aligned behavior), not an empty file.""" + content = build_ics_reminders( + basic_mode=True, + now=datetime(2026, 7, 23, 0, 0, 0, tzinfo=timezone.utc), + **_common_kwargs(), + ) + assert content is None + + +def test_build_ics_advanced_requires_threshold(): + """ADVANCED mode without a threshold is a programming error.""" + try: + build_ics_reminders(basic_mode=False, **_common_kwargs()) + except ValueError: + return + raise AssertionError("expected ValueError for missing threshold in ADVANCED") + + +# ------------------------------------------------------------------ # +# Main +# ------------------------------------------------------------------ # + +if __name__ == "__main__": + for name in sorted(dir()): + if name.startswith("test_"): + globals()[name]() + print(f" [OK] {name}") + print("[OK] All core reminders tests passed") diff --git a/tests/test_group_d_alarms.py b/tests/test_group_d_alarms.py index 430b5d5..1f32dd5 100644 --- a/tests/test_group_d_alarms.py +++ b/tests/test_group_d_alarms.py @@ -10,13 +10,12 @@ Covered behaviour: most one reminder per available day, and never returns more reminders than requested. -``compute_reminder_offsets`` lives in ``bal.gui.qt.widgets`` (which imports -PyQt6), so these tests are run headless with ``QT_QPA_PLATFORM=offscreen`` like -the other GUI tests. +``compute_reminder_offsets`` now lives in ``bal.core.reminders`` (pure, GUI-free), +so these tests run without Qt (or Electrum) at all. Run: - QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src \ - python3 -m pytest tests/test_group_d_alarms.py -q + source /home/steal/devel/bal/electrum/env/bin/activate + python3 tests/test_group_d_alarms.py """ import os @@ -25,7 +24,7 @@ import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.plugin_base import BalConfig -from bal.gui.qt.widgets import compute_reminder_offsets +from bal.core.reminders import compute_reminder_offsets # ------------------------------------------------------------------ # # Mocks diff --git a/tests/test_group_e_mock_karen7.py b/tests/test_group_e_mock_karen7.py index 22363ed..f77570c 100644 --- a/tests/test_group_e_mock_karen7.py +++ b/tests/test_group_e_mock_karen7.py @@ -37,10 +37,14 @@ import pytest # pyright: ignore[reportMissingImports] sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.heirs import Heirs +from bal.core.reminders import ( + compute_reminder_offsets, + format_time, + ical_escape, + write_temp_ics, +) from bal.core.will import HeirNotFoundException, Will, WillItem from bal.core.willexecutors import Willexecutors -from bal.gui.qt.calendar import BalCalendar -from bal.gui.qt.widgets import compute_reminder_offsets # A valid serialized Bitcoin transaction hex (1 input + 1 P2PKH output, # version 2). Reused from test_core_will.py so WillItem can parse a real tx. @@ -196,8 +200,8 @@ def test_e1_build_separate_events_for_karen7(): lines = ["BEGIN:VCALENDAR", "VERSION:2.0"] for idx, offset in enumerate(offsets, start=1): - event_dt = BalCalendar.format_time(locktime - timedelta(days=offset)) - summary = BalCalendar.ical_escape(f"{summary_base} (reminder {idx}/{total})") + event_dt = format_time(locktime - timedelta(days=offset)) + summary = ical_escape(f"{summary_base} (reminder {idx}/{total})") lines.extend([ "BEGIN:VEVENT", f"UID:bal-{wallet}-{offset}d", @@ -219,7 +223,7 @@ def test_e1_build_separate_events_for_karen7(): assert any("(reminder 1/3)" in ln for ln in lines) assert any("(reminder 3/3)" in ln for ln in lines) # The LAST event (offset 1) sits one day before the locktime. - last_dt = BalCalendar.format_time(locktime - timedelta(days=1)) + last_dt = format_time(locktime - timedelta(days=1)) assert f"DTSTART:{last_dt}" in lines @@ -227,7 +231,7 @@ def test_e1_event_description_escaping(): """Special iCalendar characters in karen7's event text are escaped so the .ics file stays valid.""" raw = "Wallet karen7; heirs: alice, bob" - escaped = BalCalendar.ical_escape(raw) + escaped = ical_escape(raw) assert "\\;" in escaped # semicolon escaped assert "\\," in escaped # comma escaped assert "karen7" in escaped @@ -245,7 +249,7 @@ def test_e1_write_temp_ics_for_karen7(): "END:VEVENT\r\n" "END:VCALENDAR\r\n" ) - path = BalCalendar.write_temp_ics(content) + path = write_temp_ics(content) try: assert os.path.isfile(path) with open(path, "rb") as f: diff --git a/tests/test_group_g_basic_calendar.py b/tests/test_group_g_basic_calendar.py index 6f40834..48128e8 100644 --- a/tests/test_group_g_basic_calendar.py +++ b/tests/test_group_g_basic_calendar.py @@ -10,13 +10,12 @@ that would fall in the past. These tests pin the behaviour of the pure helper ``basic_reminder_offsets`` that drives that decision. -``basic_reminder_offsets`` lives in ``bal.gui.qt.widgets`` (which imports -PyQt6), so these tests are run headless with ``QT_QPA_PLATFORM=offscreen`` like -the other GUI tests. +``basic_reminder_offsets`` now lives in ``bal.core.reminders`` (pure, GUI-free), +so these tests run without Qt (or Electrum) at all. Run: - QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src \ - python3 -m pytest tests/test_group_g_basic_calendar.py -q + source /home/steal/devel/bal/electrum/env/bin/activate + python3 tests/test_group_g_basic_calendar.py """ import os @@ -24,7 +23,7 @@ import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from bal.gui.qt.widgets import BASIC_REMINDER_OFFSETS, basic_reminder_offsets +from bal.core.reminders import BASIC_REMINDER_OFFSETS, basic_reminder_offsets def test_basic_offsets_all_future(): diff --git a/tests/test_group_i_basic_checkalive.py b/tests/test_group_i_basic_checkalive.py index 906e6b0..6388349 100644 --- a/tests/test_group_i_basic_checkalive.py +++ b/tests/test_group_i_basic_checkalive.py @@ -3,116 +3,99 @@ Tests for the "BASIC mode dynamic Check Alive" fix (Group I). Background (reported by the owner): with the plugin left in BASIC mode (the default), the "Check Alive" (threshold) field is hidden from the user and -stays stuck at its old/default value (roughly "today + 11 months", i.e. one -year minus the default 30-day margin). If the user then anticipates the -delivery time (locktime) to something earlier than that stale threshold - e.g. -"in 1 month" - two different checks that compare locktime against -``date_to_check`` (the resolved threshold) would incorrectly treat the will as -"expired"/"invalid", even though the whole Check Alive concept is supposed to -be inert in BASIC mode. +stays stuck at its old/default value. If the user then anticipates the +delivery time (locktime) to something earlier than that stale threshold, the +checks that compare locktime against ``date_to_check`` would incorrectly treat +the will as "expired"/"invalid", even though the whole Check Alive concept is +supposed to be inert in BASIC mode. -The fix (``BalWindow.compute_date_to_check``) makes ``date_to_check`` track -the delivery time LIVE in BASIC mode, placed a fixed 2-hour margin before it, -so it is always < locktime by construction. In ADVANCED mode nothing changes: -the stored threshold is used as-is. +The real fix lives in ``BalWindow.init_class_variables`` (window.py), which +sets ``date_to_check`` to *now* in BASIC mode, and is now implemented by the +pure policy in ``bal.core.checkalive``: -These tests call the real production method directly (no GUI/Electrum wallet -needed - it is a plain ``@staticmethod``), so they exercise the exact code -used at runtime rather than a re-implementation. + * ``resolve_date_to_check(is_basic_mode, will_settings)`` - the single + reference timestamp: "now" in BASIC, the stored threshold in ADVANCED; + * ``check_alive_expired(is_basic_mode, date_to_check)`` - whether the + Check-Alive guard should fire (never in BASIC). + +These tests exercise the exact code used at runtime (no GUI/Electrum wallet +needed, and no Qt import). Run: - PYTHONPATH=electrum-src python3 -m pytest tests/test_group_i_basic_checkalive.py -q + source /home/steal/devel/bal/electrum/env/bin/activate + python3 tests/test_group_i_basic_checkalive.py """ -import os import sys import time -sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) +sys.path.insert(0, __file__.rsplit("/", 2)[0]) -from bal.gui.qt.window import BalWindow # noqa: E402 (path insert above) - -OFFSET = BalWindow.BASIC_MODE_CHECK_ALIVE_OFFSET_SECONDS +from bal.core.checkalive import ( # noqa: E402 (path insert above) + CheckAliveError, + check_alive_expired, + resolve_date_to_check, +) -def test_offset_is_two_hours(): - """Owner-approved margin: exactly 2 hours.""" - assert OFFSET == 2 * 60 * 60 +def test_basic_mode_resolves_to_now(): + """BASIC mode: date_to_check is "now", never the stale stored threshold.""" + fake_now = 1_800_000_000.0 + result = resolve_date_to_check(True, {"threshold": time.time() + 86400}, now=fake_now) + assert result == fake_now -def _relative_days_to_midnight_timestamp(days): - """Reproduce Util.parse_locktime_string's own normalisation: "now + N - days", truncated to midnight. Used to compute the expected value in - tests without duplicating the parsing logic itself.""" - from datetime import datetime, timedelta - - now = datetime.now() - return ( - (now + timedelta(days=days)) - .replace(hour=0, minute=0, second=0, microsecond=0) - .timestamp() - ) +def test_basic_mode_ignores_stored_threshold(): + """BASIC + delivery anticipated to 1 month: date_to_check stays "now" and + does NOT jump to the old ~11-month threshold - this is the exact bug + scenario reported by the owner.""" + stale_threshold = time.time() + 11 * 30 * 86400 # the old stuck value + fake_now = 1_800_000_000.0 + result = resolve_date_to_check(True, {"threshold": stale_threshold}, now=fake_now) + assert result == fake_now -def test_basic_mode_relative_locktime_one_year(): - """BASIC + default "1y" delivery: date_to_check tracks it, 2h earlier.""" - result = BalWindow.compute_date_to_check(True, "1y", "30d") - expected_locktime = _relative_days_to_midnight_timestamp(365) - assert abs(result - (expected_locktime - OFFSET)) < 5 - - -def test_basic_mode_anticipated_delivery_stays_consistent(): - """BASIC + delivery anticipated to 1 month: date_to_check follows it, - NOT the old ~11-month threshold - this is the exact bug scenario - reported by the owner.""" - stale_threshold = "30d" # would resolve close to the OLD 1-year locktime - anticipated_locktime = "30d" # user moved delivery to ~1 month from now - result = BalWindow.compute_date_to_check( - True, anticipated_locktime, stale_threshold - ) - locktime_ts = _relative_days_to_midnight_timestamp(30) - # date_to_check must be (delivery - 2h), always strictly before delivery. - assert result < locktime_ts - assert abs((locktime_ts - OFFSET) - result) < 5 - - -def test_basic_mode_date_to_check_always_before_locktime(): - """Regression guard for the reported bug: whatever the delivery date is - (even very close to "now"), date_to_check must stay before it.""" - for relative_locktime in ("1d", "7d", "30d", "90d", "365d"): - result = BalWindow.compute_date_to_check(True, relative_locktime, "30d") - parsed_locktime = _relative_days_to_midnight_timestamp( - int(relative_locktime[:-1]) - ) - assert result < parsed_locktime, ( - f"date_to_check ({result}) should be before locktime " - f"({parsed_locktime}) for locktime={relative_locktime}" - ) - - -def test_advanced_mode_uses_stored_threshold_unchanged(): - """ADVANCED mode: behaviour must stay exactly as before this fix - the - stored threshold is used as-is, regardless of the locktime value.""" +def test_advanced_mode_uses_stored_threshold(): + """ADVANCED mode: behaviour stays exactly as before - the stored threshold + is used as-is, regardless of the locktime value.""" absolute_threshold = time.time() + 5 * 86400 # arbitrary user-chosen value - result = BalWindow.compute_date_to_check(False, "30d", absolute_threshold) + result = resolve_date_to_check(False, {"threshold": absolute_threshold}) assert abs(result - absolute_threshold) < 1 -def test_basic_mode_falls_back_on_unparsable_locktime(): - """If the locktime can't be parsed for any reason, BASIC mode must not - crash: it falls back to the stored threshold, same as ADVANCED.""" - absolute_threshold = time.time() + 5 * 86400 - result = BalWindow.compute_date_to_check( - True, {"not": "a valid locktime"}, absolute_threshold - ) - assert abs(result - absolute_threshold) < 1 +def test_advanced_mode_parses_relative_threshold(): + """ADVANCED + relative threshold ("30d") resolves to a future timestamp.""" + result = resolve_date_to_check(False, {"threshold": "30d"}) + assert result > time.time() + + +def test_basic_mode_never_expired(): + """BASIC mode can never be "expired": a passed check-alive date must never + force a postpone/rewrite of the will.""" + assert check_alive_expired(True, time.time() - 10_000) is False + assert check_alive_expired(True, 1_000_000_000.0) is False + + +def test_advanced_mode_expired_when_past(): + assert check_alive_expired(False, time.time() - 10_000) is True + + +def test_advanced_mode_not_expired_when_future(): + assert check_alive_expired(False, time.time() + 10_000) is False + + +def test_basic_mode_never_raises_check_alive_error(): + """Regression guard for the reported bug: whatever the delivery date, + BASIC mode never raises CheckAliveError (the postpone/invalidate trigger).""" + date_to_check = resolve_date_to_check(True, {"threshold": "30d"}) + assert not check_alive_expired(True, date_to_check) + # If it did fire, this is the exact exception that would be raised. + assert issubclass(CheckAliveError, Exception) if __name__ == "__main__": - test_offset_is_two_hours() - test_basic_mode_relative_locktime_one_year() - test_basic_mode_anticipated_delivery_stays_consistent() - test_basic_mode_date_to_check_always_before_locktime() - test_advanced_mode_uses_stored_threshold_unchanged() - test_basic_mode_falls_back_on_unparsable_locktime() - print("All test_group_i_basic_checkalive tests passed.") + for name in sorted(dir()): + if name.startswith("test_"): + globals()[name]() + print(f" [OK] {name}") + print("[OK] All test_group_i_basic_checkalive tests passed.") diff --git a/tests/test_gui_calendar.py b/tests/test_gui_calendar.py index 7d1b1e0..6ce2769 100644 --- a/tests/test_gui_calendar.py +++ b/tests/test_gui_calendar.py @@ -1,126 +1,25 @@ """ Tests for ``bal.gui.qt.calendar``. -Covers BalCalendar static methods: format_time, ical_escape, fold_ical_line, -write_temp_ics, open_with_default_app. +Covers the GUI/OS glue that stayed in this module: BalCalendar.open_with_default_app. +The RFC-5545 helpers (format_time, ical_escape, fold_ical_line, write_temp_ics) +moved to ``bal.core.reminders`` and are tested in ``tests/test_core_reminders.py``. Run: QT_QPA_PLATFORM=offscreen python3 tests/test_gui_calendar.py """ -import os import sys -from datetime import datetime, timezone sys.path.insert(0, __file__.rsplit("/", 2)[0]) from bal.gui.qt.calendar import BalCalendar -# ------------------------------------------------------------------ # -# format_time -# ------------------------------------------------------------------ # - -def test_format_time_utc(): - dt = datetime(2025, 6, 1, 12, 30, 45, tzinfo=timezone.utc) - assert BalCalendar.format_time(dt) == "20250601T123045Z" - - -def test_format_time_non_utc(): - from datetime import timedelta - tz = timezone(timedelta(hours=2)) - dt = datetime(2025, 1, 15, 8, 0, 0, tzinfo=tz) - result = BalCalendar.format_time(dt) - assert result.endswith("Z") - assert result == "20250115T060000Z" - - -# ------------------------------------------------------------------ # -# ical_escape -# ------------------------------------------------------------------ # - -def test_ical_escape_no_change(): - text = "hello world" - assert BalCalendar.ical_escape(text) == "hello world" - - -def test_ical_escape_backslash(): - assert BalCalendar.ical_escape("a\\b") == "a\\\\b" - - -def test_ical_escape_semicolon(): - assert BalCalendar.ical_escape("a;b") == "a\\;b" - - -def test_ical_escape_comma(): - assert BalCalendar.ical_escape("a,b") == "a\\,b" - - -def test_ical_escape_multiline(): - text = "line1\r\nline2" - result = BalCalendar.ical_escape(text) - assert "\r\n" in result - assert "line1" in result - assert "line2" in result - - -def test_ical_escape_all(): - text = "\\;," - assert BalCalendar.ical_escape(text) == "\\\\\\;\\," - - -# ------------------------------------------------------------------ # -# fold_ical_line -# ------------------------------------------------------------------ # - -def test_fold_ical_line_short(): - line = "SUMMARY:Test" - assert BalCalendar.fold_ical_line(line) == "SUMMARY:Test" - - -def test_fold_ical_line_long(): - line = "X-LONG:" + "a" * 100 - result = BalCalendar.fold_ical_line(line, limit=75) - parts = result.split("\r\n ") - assert len(parts) > 1 - assert result.startswith("X-LONG:") - - -def test_fold_ical_line_unicode(): - line = "DESCRIPTION:" + "\u20ac" * 40 - result = BalCalendar.fold_ical_line(line, limit=75) - assert "\r\n " in result - assert "\u20ac" in result - - -# ------------------------------------------------------------------ # -# write_temp_ics -# ------------------------------------------------------------------ # - -def test_write_temp_ics(): - content = "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n" - path = BalCalendar.write_temp_ics(content) - try: - assert os.path.isfile(path) - with open(path, "rb") as f: - assert f.read() == content.encode("utf-8") - finally: - os.unlink(path) - - -def test_write_temp_ics_empty(): - path = BalCalendar.write_temp_ics("") - try: - assert os.path.isfile(path) - with open(path, "rb") as f: - assert f.read() == b"" - finally: - os.unlink(path) - - # ------------------------------------------------------------------ # # open_with_default_app # ------------------------------------------------------------------ # + def test_open_with_default_app_not_found(): result = BalCalendar.open_with_default_app( "/nonexistent/calendar_app", "/tmp/fake.ics" diff --git a/tests/test_gui_common.py b/tests/test_gui_common.py index 77eb2b8..324ec42 100644 --- a/tests/test_gui_common.py +++ b/tests/test_gui_common.py @@ -1,7 +1,8 @@ """ Tests for ``bal.gui.qt.common``. -Covers shown_cv, CheckAliveError, add_widget, log_error, export_meta_gui. +Covers shown_cv, add_widget, log_error, export_meta_gui (and the +CheckAliveError exception, which now lives in ``bal.core.checkalive``). Run: QT_QPA_PLATFORM=offscreen python3 tests/test_gui_common.py @@ -44,23 +45,30 @@ def test_shown_cv_roundtrip(): # ------------------------------------------------------------------ # -# CheckAliveError +# CheckAliveError (moved to bal.core.checkalive; kept tested via the +# common->core import chain) # ------------------------------------------------------------------ # def test_check_alive_error_default(): - err = common.CheckAliveError(1000000) + from bal.core.checkalive import CheckAliveError + + err = CheckAliveError(1000000) assert err.timestamp_to_check == 1000000 def test_check_alive_error_str(): - err = common.CheckAliveError(1000000) + from bal.core.checkalive import CheckAliveError + + err = CheckAliveError(1000000) s = str(err) assert "Check alive expired" in s assert "1970" in s def test_check_alive_error_subclass(): - assert issubclass(common.CheckAliveError, Exception) + from bal.core.checkalive import CheckAliveError + + assert issubclass(CheckAliveError, Exception) # ------------------------------------------------------------------ # diff --git a/tests/test_gui_widgets.py b/tests/test_gui_widgets.py index 89f33ab..b6a71de 100644 --- a/tests/test_gui_widgets.py +++ b/tests/test_gui_widgets.py @@ -118,23 +118,25 @@ def test_locktime_editor_min_max(): def test_locktime_raw_edit_replace_str(): # replace_str only strips the day ("d") and year ("y") suffixes. The # block-height suffix ("b") was removed (A1), so "b" is NOT stripped - # anymore (locktimes are always UNIX timestamps now). - from bal.gui.qt.widgets import LockTimeRawEdit - assert LockTimeRawEdit.replace_str("123d") == "123" - assert LockTimeRawEdit.replace_str("456y") == "456" + # anymore (locktimes are always UNIX timestamps now). The helper moved to + # bal.core.input_rules (replace_dy_suffixes); the widget delegates to it. + from bal.core.input_rules import replace_dy_suffixes + assert replace_dy_suffixes("123d") == "123" + assert replace_dy_suffixes("456y") == "456" # "b" is left untouched (no longer a recognised suffix) - assert LockTimeRawEdit.replace_str("789b") == "789b" + assert replace_dy_suffixes("789b") == "789b" # only d/y are stripped; a stray "b" remains - assert LockTimeRawEdit.replace_str("12d34y56b") == "123456b" + assert replace_dy_suffixes("12d34y56b") == "123456b" def test_locktime_raw_edit_checkbdy(): - from bal.gui.qt.widgets import LockTimeRawEdit + # checkbdy moved to bal.core.input_rules (_checkbdy). + from bal.core.input_rules import _checkbdy # character at expected position matches appendix - pos, s = LockTimeRawEdit.checkbdy(None, "123d", 4, "d") + pos, s = _checkbdy("123d", 4, "d") assert s == "123d" # character at expected position does not match - pos, s = LockTimeRawEdit.checkbdy(None, "123x", 4, "d") + pos, s = _checkbdy("123x", 4, "d") assert s == "123x" diff --git a/tests/test_import_will_details.py b/tests/test_import_will_details.py index 7c9d5e6..2ce01f3 100644 --- a/tests/test_import_will_details.py +++ b/tests/test_import_will_details.py @@ -305,10 +305,14 @@ def _make_merge_fake(willitems): wallet=FakeWallet(), bal_window=None, date_to_check=1700000000, + will_settings={"threshold": 1700000000}, bal_plugin=SimpleNamespace( HISTORY_LABEL=SimpleNamespace( get=lambda: "BAL will history ({willexecutor})" - ) + ), + # BASIC is the default user type; merge_will resolves date_to_check + # through bal.core.checkalive when it is missing. + is_basic_mode=lambda: True, ), update_all=lambda: calls.append("update_all"), )