From 3d9757fe2d4890d1f0e1b6ebf3ef8c2685f8a6a6 Mon Sep 17 00:00:00 2001 From: svatantrya Date: Mon, 12 Oct 2026 15:16:50 -0400 Subject: [PATCH] core+gui+cli: network-fee invalidation, will-tree fixes, i18n refresh - #64: the on-chain invalidation now pays the current network fee through a configurable fee-policy setting (default eta:2); baltx_fees is only the offline fallback. The change output is dust-guarded: coins are appended until the change clears the wallet dust threshold. - #65: a will item whose father is missing/stale is handled defensively in check_invalidated and discarded by add_willtree instead of raising KeyError. - #66: children are stored as a list (dict accepted for older data/tests). - Refresh the translation catalogs and fix stale source line references. - New tests for the invalidation fee policy, the dust guard and the father link handling. --- CHANGELOG.md | 147 ++++++ bal/cli/controller.py | 1 + bal/core/plugin_base.py | 18 + bal/core/will.py | 276 +++++++++-- bal/gui/qt/dialogs.py | 2 + bal/gui/qt/plugin.py | 80 ++- bal/gui/qt/window.py | 2 + bal/locale/bal.pot | 613 ++++++++++++----------- bal/locale/es_ES/LC_MESSAGES/bal.po | 614 ++++++++++++------------ bal/locale/it_IT/LC_MESSAGES/bal.po | 614 ++++++++++++------------ tests/test_core_invalidation_fee.py | 296 ++++++++++++ tests/test_core_will_invalidate.py | 254 +++++++++- tests/test_core_willtree_father.py | 145 ++++++ tests/test_settings_invalidation_fee.py | 208 ++++++++ 14 files changed, 2296 insertions(+), 974 deletions(-) create mode 100644 tests/test_core_invalidation_fee.py create mode 100644 tests/test_core_willtree_father.py create mode 100644 tests/test_settings_invalidation_fee.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c097f2..b295aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3612,3 +3612,150 @@ deselected executor) are not part of it and will be proposed separately. none new. **Outcome:** DONE (pending Truman's review). + +--- + +## 64. Invalidation pays the current network fee, configurable via a fee-policy setting + +**Date:** 2026-10-10 + +**Goal (owner request):** the will's `baltx_fees` (default 20 sat/vbyte) is +deliberately high because the inheritance transactions may be broadcast by the +will-executors years from now, when network fees may be much higher. The +on-chain INVALIDATION, instead, is sent TODAY: it spends the will's coins back +to the wallet so the old pre-signed inheritance transactions become unusable. +It must therefore pay the current network fee, not the inheritance rate. After +the invalidation the plugin rebuilds the new inheritance with `baltx_fees` as +before (unchanged). The fee policy is exposed as an ADVANCED-only setting. + +**What changed:** + +- `bal/core/will.py` + - New constants `INVALIDATION_FEE_ETA_BLOCKS = 2` and + `INVALIDATION_FEE_POLICY_DEFAULT = "eta:2"` (owner decision: confirm within + 2 blocks, Electrum's own default target). + - New `Will.get_invalidation_fee_rate(wallet, fallback_fee_per_byte, + fee_policy_descriptor=None)`: builds an Electrum `FeePolicy` from the + descriptor (Feerate / ETA / Mempool) and returns its rate in sat/vbyte, + together with the `FeeMethod` name (`"feerate"` / `"eta"` / `"mempool"`) or + `"fallback"`. The fallback (the will's `baltx_fees`, i.e. the previous + behaviour) is used when no usable rate is available: offline wallet, + estimates not received yet, network error or an invalid rate value. A + malformed stored descriptor is handled by Electrum's `FeePolicy`, which + falls back to the default `eta:2` policy (network rate), not to + `baltx_fees`. Never raises. + - `Will.invalidate_will` gained a `fee_policy_descriptor` argument and uses + that rate; the total fee is rounded UP to whole satoshis (network rates can + be fractional); it logs the chosen rate and its source. `fees_per_byte` is + now only the fallback, so every caller keeps working. The change output must + stay at or above the wallet dust threshold: coins are appended until the + change clears dust, and if it never does the invalidation returns `None` + (instead of an unrelayable sub-dust output). Docstrings added. +- `bal/core/plugin_base.py`: new `INVALIDATION_FEE_POLICY` BalConfig + (`bal_invalidation_fee_policy`, default `"eta:2"`). +- `bal/gui/qt/plugin.py`: new ADVANCED-only row 16 in the settings dialog, + reusing Electrum's standard fee widget (`FeeSlider` + `FeeComboBox`) for the + Feerate / ETA / Mempool choice, with a Help button and a per-row reset. The + chosen descriptor is persisted; the dialog-wide "Reset to Default Setting" + also restores it. +- `bal/gui/qt/window.py`, `bal/gui/qt/dialogs.py`, `bal/cli/controller.py`: + every `Will.invalidate_will` call site passes + `fee_policy_descriptor=...INVALIDATION_FEE_POLICY.get()`, so the Tools → + Invalidate action, the wizard postpone/expired paths, the automatic + invalidation and the CLI (`bal_will_invalidate`) all get the new behaviour. +- `bal/locale/bal.pot` and the `es_ES` / `it_IT` catalogs refreshed + (`pybabel extract` / `update`, Babel 2.10.3 to match the existing template) so + the new settings-dialog strings are translatable. + +**Why no special case for an already-expired will:** discussed with the owner +and intentionally left out. Before its locktime the old inheritance tx cannot +enter a block or the mempool, so the invalidation has no competitor. RBF stays +enabled on the invalidation, so its fee can be raised from Electrum's History +("Increase fee") if it ever gets stuck. + +**Verification:** + +- `tests/test_core_invalidation_fee.py` (new, 17 tests): rate selection + (2-block target, network value used as is also above `baltx_fees`, fractional + rates, fallback when offline / no estimate / error / invalid values / invalid + descriptor / no wallet) and the fee actually deducted by `invalidate_will` + (network vs fallback, fixed feerate offline, rounding up). +- `tests/test_settings_invalidation_fee.py` (new, 7 tests): the row is hidden + in BASIC and visible in ADVANCED; choosing a Feerate / ETA policy persists + the descriptor; "Reset to Default Setting" restores `"eta:2"`. +- `tests/test_core_will_invalidate.py`: its mock wallet now has `network=None` + so the pure fee arithmetic (`fee = size * fees_per_byte`) still exercises the + fallback path; the policy-based selection is covered by the new file. Four new + tests cover the dust guard: sub-dust change returns `None`, change exactly at + the threshold is accepted, the fallback stops when the change is still dust, + and it succeeds once the change clears dust. +- Selected suite (core + GUI + settings + translations): 423 passed. +- `ruff check` on the touched files: no new violations (only the pre-existing + I001 in `dialogs.py`). + +**Outcome:** DONE (pending the owner's test of the release ZIP). + +--- + +## 65. Check crashed with `KeyError: ''` (stale "father" link in the will tree) + +**Date:** 2026-10-10 + +**Symptom (owner's mainnet wallet, Electrum 4.8.x):** the Check wizard stopped +at "Checking your will" with `Error: 'a65a682f…'`, and the window refresh +logged "error while updating window" with the same txid: + +``` +File "bal/core/will.py", line 657, in check_invalidated + or willtree[w.father].get_status("CONFIRMED") +KeyError: 'a65a682f2975f0dffd9eccce9392833f227badf617fc67815674736d594d4115' +``` + +**Root cause:** `father` is persisted in the wallet (the `"Father"` field of +`WillItem.to_dict`) and was only ever set, never cleared. When the father tx was +later removed from the will (rebuild, merge, replacement) the child kept +pointing to a txid that no longer exists, and `check_invalidated` crashed with a +`KeyError` on that txid (the Check wizard and the window refresh both failed). + +**What changed:** + +- `bal/core/will.py` + - `Will.add_willtree`: clears `father` on every item before rebuilding the + links, so the tree is always recomputed from scratch from the transactions + themselves. Docstring added explaining WHY. + - `Will.check_invalidated`: a father that is not (or no longer) part of the + will is treated as "no father" instead of raising `KeyError`. +- `tests/test_core_willtree_father.py` (new, 7 tests): stale father discarded / + replaced by the real parent, no crash in `check_invalidated` (with and + without the tree rebuild), real parent/child link built, child of an + unconfirmed parent still skipped, and the `set_invalidate` cascade (see #66). + +**Verification:** the new test file plus the core/CLI suites pass; `ruff check` +clean on the touched files. + +**Outcome:** DONE (pending the owner's test of the ZIP). + +--- + +## 66. Invalidation cascade crashed on real will trees (`children` is a list) + +**Date:** 2026-10-10 + +**Problem (found while fixing #65, reproduced on real data):** +`Will.set_invalidate` iterated `children.items()`, but `Will.add_willtree` +builds `children` as a LIST of `[child_id, input_idx, prevout_idx]`, so +invalidating a will tx that has child will txs raised +`AttributeError: 'list' object has no attribute 'items'`. The existing tests in +`test_core_will_invalidate.py` set `children` to dicts by hand, so they never +saw it. + +**What changed:** `bal/core/will.py` - `set_invalidate` accepts both shapes +(the list from `add_willtree`, a dict from older data/tests) and only recurses +into children that are still present in the will. +`tests/test_core_willtree_father.py`: new test +`test_set_invalidate_cascades_to_children_built_by_add_willtree`. + +**Verification:** `tests/test_core_willtree_father.py` passes; `ruff check` +clean on the touched files. + +**Outcome:** DONE. diff --git a/bal/cli/controller.py b/bal/cli/controller.py index 3722668..5aecf0e 100644 --- a/bal/cli/controller.py +++ b/bal/cli/controller.py @@ -417,6 +417,7 @@ class BalController: will_locktime=Will.get_min_locktime( willitems, default_value=self.date_to_check ), + fee_policy_descriptor=self.plugin.INVALIDATION_FEE_POLICY.get(), ) if tx is not None: try: diff --git a/bal/core/plugin_base.py b/bal/core/plugin_base.py index 37a853b..5e0ce1e 100644 --- a/bal/core/plugin_base.py +++ b/bal/core/plugin_base.py @@ -251,6 +251,24 @@ class BalPlugin(BasePlugin): self.TX_FEES = BalConfig(config, "bal_tx_fees", 100) self.INVALIDATE = BalConfig(config, "bal_invalidate", True) self.ASK_INVALIDATE = BalConfig(config, "bal_ask_invalidate", True) + + # INVALIDATION_FEE_POLICY (advanced): the fee rate the on-chain + # INVALIDATION transaction pays. It is expressed as an Electrum + # ``FeePolicy`` descriptor ":", the same format Electrum + # uses for its own ``FEE_POLICY`` setting, so the standard fee slider + # (Feerate / ETA / Mempool) can edit it directly: + # * "eta:2" - fee estimated for confirmation within 2 blocks + # (the default: Electrum's own default target); + # * "mempool:N" - fee targeting a depth of N vbytes in the mempool; + # * "feerate:N" - a fixed rate of N sat/kvbyte (works offline). + # WHY it is separate from the will's ``baltx_fees``: the will is built + # with a deliberately high rate because the will-executors may broadcast + # the inheritance years from now, while the invalidation is sent TODAY + # and must pay the current network fee. ``baltx_fees`` remains only the + # fallback used when no estimate is available. + self.INVALIDATION_FEE_POLICY = BalConfig( + config, "bal_invalidation_fee_policy", "eta:2" + ) self.PREVIEW = BalConfig(config, "bal_preview", True) self.SAVE_TXS = BalConfig(config, "bal_save_txs", True) diff --git a/bal/core/will.py b/bal/core/will.py index 1c205c0..df6cfc4 100644 --- a/bal/core/will.py +++ b/bal/core/will.py @@ -26,9 +26,11 @@ The status flags themselves (the source of truth) stay here; only the mapping "status -> colour" now lives in the GUI layer. No behaviour changed. """ +import math from datetime import datetime, timezone from electrum.address_synchronizer import TX_HEIGHT_FUTURE, TX_HEIGHT_LOCAL +from electrum.fee_policy import FeePolicy from electrum.logging import Logger, get_logger from electrum.transaction import ( PartialTransaction, @@ -50,6 +52,18 @@ from .willexecutors import Willexecutors MIN_LOCKTIME = 1 MIN_BLOCK = 1 +# Confirmation target (in blocks) for the on-chain INVALIDATION transaction. +# WHY: the invalidation is sent TODAY (it spends the will's coins so the old +# pre-signed inheritance txs become unusable), so it must pay the CURRENT +# network fee for a quick confirmation - not ``baltx_fees``, which is a +# deliberately high rate reserved for inheritance txs that may be broadcast +# years from now. 2 blocks is Electrum's own default target (owner decision). +INVALIDATION_FEE_ETA_BLOCKS = 2 +# Default fee policy for the invalidation tx, as an Electrum ``FeePolicy`` +# descriptor (":"). The user can change it from the plugin's +# graphical settings, which uses Electrum's standard fee slider (Feerate / ETA / +# Mempool). "eta:2" = fee estimated for confirmation within 2 blocks. +INVALIDATION_FEE_POLICY_DEFAULT = f"eta:{INVALIDATION_FEE_ETA_BLOCKS}" _logger = get_logger(__name__) @@ -68,6 +82,22 @@ class Will: # build a tree with parent transactions @staticmethod def add_willtree(will): + """(Re)build the parent/child links between the will transactions. + + Every item gets ``children`` (the will txs spending its outputs) and, + when it spends the output of another will tx, a ``father``. + + WHY the reset below: ``father`` is persisted in the wallet (the + ``"Father"`` field of ``WillItem.to_dict``) and was only ever set, + never cleared. When the father tx was later removed from the will + (rebuild, merge, replacement) the child kept pointing to a txid that + no longer exists, and ``check_invalidated`` crashed with a + ``KeyError`` on that txid (reported on a real wallet: the Check + wizard and the window refresh both failed). The tree is therefore + always recomputed from scratch from the transactions themselves. + """ + for willid in will: + will[willid].father = None for willid in will: will[willid].children = Will.get_children(will, willid) for child in will[willid].children: @@ -438,61 +468,220 @@ class Will: out[inp.prevout.to_str()] = inp return out + @staticmethod + def get_invalidation_fee_rate( + wallet, fallback_fee_per_byte, fee_policy_descriptor=None + ): + """Return ``(fee_rate, source)`` for the on-chain invalidation tx. + + ``fee_rate`` is in sat/vbyte. ``source`` names where the rate came from: + the ``FeeMethod`` used by the policy (``"feerate"``, ``"eta"``, + ``"mempool"``) or ``"fallback"`` when no usable rate was available. + + WHY: the invalidation is broadcast NOW, so it pays the current network + fee according to the user's chosen ``FeePolicy`` (default ``"eta:2"``, + i.e. a confirmation within 2 blocks). The will's ``baltx_fees`` is kept + only as a fallback, used when no estimate is available (offline wallet, + estimates not received yet, server error), which is exactly the + behaviour of previous versions. + + The rate is computed by Electrum's own ``FeePolicy`` (the module behind + the standard fee slider), so the graphical settings can offer Feerate / + ETA / Mempool. It returns sat/kvbyte and already clamps dynamic + estimates between Electrum's conservative minimum relay fee and its + maximum dynamic fee; a fixed "feerate" policy needs no network at all. + This method never raises. + + Args: + wallet: The Electrum wallet (its ``network`` may be ``None``). + fallback_fee_per_byte: Rate (sat/vbyte) used when there is no + usable rate - normally the will's ``baltx_fees``. + fee_policy_descriptor: Electrum fee policy descriptor (e.g. + ``"eta:2"``). ``None`` uses ``INVALIDATION_FEE_POLICY_DEFAULT``. + """ + descriptor = fee_policy_descriptor or INVALIDATION_FEE_POLICY_DEFAULT + # FeePolicy.parse handles malformed descriptors internally (it logs a + # warning and falls back to "eta:2"), so this never raises. + policy = FeePolicy(descriptor) + network = getattr(wallet, "network", None) if wallet else None + fee_per_kb = None + try: + fee_per_kb = policy.fee_per_kb(network) + except Exception as e: + _logger.warning(f"invalidation fee: no estimate ({e!r})") + fee_per_kb = None + # Accept only a real positive integer (Electrum's type for sat/kvbyte); + # ``bool`` is excluded explicitly because it is a subclass of ``int``. + if ( + isinstance(fee_per_kb, int) + and not isinstance(fee_per_kb, bool) + and fee_per_kb > 0 + ): + return fee_per_kb / 1000, policy.method.name.lower() + return fallback_fee_per_byte, "fallback" + + @staticmethod + def _invalidation_age_key(utxo): + """Sort key for invalidation coins: smaller = in the wallet longest. + + "Oldest" is approximated by the funding transaction's confirmation + height (confirmed coins with the lowest height were received first). + Unconfirmed, local or unknown-height coins have no mined position and + are treated as the newest. Deterministic tie-breakers: position within + the block, then the prevout string. + """ + height = getattr(utxo, "block_height", None) + if isinstance(height, int) and height > 0: + txpos = getattr(utxo, "block_txpos", 0) or 0 + return (0, height, txpos, utxo.prevout.to_str()) + return (1, 0, 0, utxo.prevout.to_str()) + @staticmethod def invalidate_will(will, wallet, fees_per_byte, history_label=None, - will_locktime=None): + will_locktime=None, fee_policy_descriptor=None): + """Build the (unsigned) on-chain invalidation transaction. + + The transaction spends, per will transaction, only the OLDEST funding + coin (the one that has been in the wallet longest): spending a single + input already makes the pre-signed transaction unspendable, while the + newer coins stay available for the rebuilt (postponed) will. Both VALID + and REPLACED will items are considered, because a REPLACED item's + pre-signed transaction may have been pushed to the will-executors and + may spend different coins. The outputs go back to one of the wallet's + own change addresses. When the chosen coins would not cover the fee + while leaving a change output at or above the dust threshold, additional + (newer) candidate coins are added as fallback inputs; if the change + still cannot clear dust once the coins are exhausted, no invalidation + transaction is returned. + + Fee: the CURRENT network fee according to the user's fee policy + (``fee_policy_descriptor``, default ``"eta:2"``; see + :meth:`get_invalidation_fee_rate`). ``fees_per_byte`` (the will's + ``baltx_fees``) is only the FALLBACK used when no usable rate is + available. RBF stays enabled, so the fee can still be raised from + Electrum's History ("Increase fee") if the transaction gets stuck. + + + Returns the ``PartialTransaction``, or ``None`` when there is nothing + to invalidate or the balance does not cover the fee. + """ _logger.debug("invalidate tx in will module") - will_only_valid = Will.only_valid_list(will) - inputs = Will.get_all_inputs(will_only_valid) + # Invalidating only the VALID items is not enough. A will rebuilt after + # an heir change (or a rebuild) supersedes the previously signed + # transaction: the old item is kept but marked REPLACED, which CLEARS + # VALID (see set_status). That old transaction may have been pushed to + # the will-executors and may spend DIFFERENT coins than the current + # valid one, so its funding UTXOs must be spent too, otherwise a + # will-executor could still broadcast it from a UTXO left unspent. + # REPLACED items therefore join the VALID ones; inputs that are already + # spent are naturally dropped by the wallet-UTXO filter below. + will_to_invalidate = { + wid: wi + for wid, wi in will.items() + if wi.get_status("VALID") or wi.get_status("REPLACED") + } if history_label is not None and will_locktime is not None: utxos = Util.get_available_utxos(wallet, history_label, will_locktime) else: utxos = wallet.get_utxos() - filtered_inputs = [] - prevout_to_spend = [] current_height = Util.get_current_height(wallet.network) - for prevout_str, ws in inputs.items(): - for w in ws: - if w[0] not in filtered_inputs: - filtered_inputs.append(w[0]) - if prevout_str not in prevout_to_spend: - prevout_to_spend.append(prevout_str) - balance = 0 - utxo_to_spend = [] + utxo_by_prevout = {} for utxo in utxos: if utxo.is_coinbase_output() and utxo.block_height < current_height+100: _logger.debug("is not mature coinbase output") continue - utxo_str = utxo.prevout.to_str() - if utxo_str in prevout_to_spend: - balance += utxo.value_sats() - utxo_to_spend.append(utxo) + utxo_by_prevout[utxo.prevout.to_str()] = utxo + # Candidate coins: the wallet UTXOs that fund one of the will txs. + candidates = [] + seen = set() + for wi in will_to_invalidate.values(): + for txin in wi.tx.inputs(): + prevout_str = txin.prevout.to_str() + if prevout_str in seen or prevout_str not in utxo_by_prevout: + continue + seen.add(prevout_str) + candidates.append(utxo_by_prevout[prevout_str]) + # Spend, per will transaction, only its OLDEST funding coin (the one + # that has been in the wallet longest): spending a single input is + # enough to make the pre-signed transaction unspendable, and the newer + # coins stay available for the rebuilt (postponed) will. + chosen = {} # prevout_str -> UTXO, one (the oldest) per will transaction + for wi in will_to_invalidate.values(): + item_candidates = [ + utxo_by_prevout[txin.prevout.to_str()] + for txin in wi.tx.inputs() + if txin.prevout.to_str() in utxo_by_prevout + ] + if item_candidates: + oldest = min(item_candidates, key=Will._invalidation_age_key) + chosen[oldest.prevout.to_str()] = oldest + utxo_to_spend = list(chosen.values()) + balance = sum(u.value_sats() for u in utxo_to_spend) _logger.debug("utxo to spend: {}".format(utxo_to_spend)) if len(utxo_to_spend) > 0: change_addresses = wallet.get_change_addresses_for_new_transaction() - out = PartialTxOutput.from_address_and_value(change_addresses[0], balance) - out.is_change = True locktime = current_height - tx = PartialTransaction.from_io( - utxo_to_spend, [out], locktime=locktime, version=2 + fee_rate, fee_source = Will.get_invalidation_fee_rate( + wallet, fees_per_byte, fee_policy_descriptor ) - tx.set_rbf(True) - fee = tx.estimated_size() * fees_per_byte - if balance - fee > 0: - out = PartialTxOutput.from_address_and_value( - change_addresses[0], balance - fee - ) + _logger.info( + f"invalidation fee: {fee_rate} sat/vbyte ({fee_source})" + ) + # The change output must stay at or above the dust threshold, + # otherwise the invalidation transaction would be non-standard and + # could not be relayed. + try: + dust = int(wallet.dust_threshold()) + except (TypeError, ValueError): + dust = 0 + # Remaining candidate coins, oldest first, for the fee fallback. + remaining = sorted( + (c for c in candidates if c.prevout.to_str() not in chosen), + key=Will._invalidation_age_key, + ) + while True: + # The network rate can be fractional (e.g. 1.5 sat/vbyte): round + # the total fee UP to whole satoshis so the output value is an + # integer and the rate is never below the one requested. + out = PartialTxOutput.from_address_and_value(change_addresses[0], balance) + out.is_change = True tx = PartialTransaction.from_io( utxo_to_spend, [out], locktime=locktime, version=2 ) tx.set_rbf(True) + fee = int(math.ceil(tx.estimated_size() * fee_rate)) + if balance - fee >= dust: + out = PartialTxOutput.from_address_and_value( + change_addresses[0], balance - fee + ) + tx = PartialTransaction.from_io( + utxo_to_spend, [out], locktime=locktime, version=2 + ) + tx.set_rbf(True) - _logger.debug(f"invalidation tx: {tx}") - return tx + _logger.debug(f"invalidation tx: {tx}") + return tx - else: - _logger.debug(f"balance({balance}) - fee({fee}) <=0") - pass + # The selected coins cannot cover the fee while leaving a + # spendable (non-dust) change: keep spending the next-oldest + # will coin until the invalidation is economically viable or no + # coin is left. + if not remaining: + _logger.debug( + "balance({})-fee({}):"). # + # ----------------------------------------------------------------- # + invalidation_fee_policy = FeePolicy(self.INVALIDATION_FEE_POLICY.get()) + settings_network = ( + getattr(window, "network", None) if window is not None else None + ) + + def on_invalidation_fee_change(_fee_rate): + """Persist the fee policy descriptor chosen with the slider.""" + self.INVALIDATION_FEE_POLICY.set( + invalidation_fee_policy.get_descriptor() + ) + + invalidation_fee_slider = FeeSlider( + parent=d, + network=settings_network, + fee_policy=invalidation_fee_policy, + callback=on_invalidation_fee_change, + ) + invalidation_fee_combo = FeeComboBox(invalidation_fee_slider) + # Do NOT call invalidation_fee_slider.update() here: the slider and combo + # already initialise themselves before their signals are connected, and + # update() fires moved() -> on_invalidation_fee_change, which would + # persist (and snap) the descriptor just for opening the dialog. + invalidation_fee_box = QWidget() + invalidation_fee_box_layout = QHBoxLayout(invalidation_fee_box) + invalidation_fee_box_layout.setContentsMargins(0, 0, 0, 0) + invalidation_fee_box_layout.addWidget(invalidation_fee_slider, 1) + invalidation_fee_box_layout.addWidget(invalidation_fee_combo) + + def reset_invalidation_fee(): + """Restore the fee policy to its default ("eta:2"). + + The live ``FeePolicy`` object is mutated in place (method + value) + so the slider keeps working with the new defaults; the stored + descriptor and the combo are then re-synced. + """ + default_policy = FeePolicy(self.INVALIDATION_FEE_POLICY.default) + invalidation_fee_policy.method = default_policy.method + invalidation_fee_policy.value = default_policy.value + self.INVALIDATION_FEE_POLICY.set(self.INVALIDATION_FEE_POLICY.default) + invalidation_fee_combo.setCurrentIndex( + FeeMethod.slider_index_of_method(default_policy.method) + ) + invalidation_fee_slider.update() + + lbl_invalidation_fee = QLabel(_("Invalidation fee policy")) + help_invalidation_fee = HelpButton(_( + "Fee rate the on-chain invalidation transaction pays.\n" + "The invalidation is sent TODAY (it spends the will's coins so the " + "old inheritance transactions become unusable), so it should pay " + "the current network fee - not the will's mining fee rate.\n\n" + "Feerate: a fixed rate (sat/vByte).\n" + "ETA: fee based on the estimated confirmation time (blocks).\n" + "Mempool: fee targeting a depth in the memory pool.\n" + "Only used in ADVANCED mode." + )) + btn_reset_invalidation_fee = QPushButton("\u21ba") + btn_reset_invalidation_fee.setFixedWidth(24) + btn_reset_invalidation_fee.setToolTip(_("Reset to default")) + btn_reset_invalidation_fee.clicked.connect(reset_invalidation_fee) + grid.addWidget(_hide_if_basic(lbl_invalidation_fee), 16, 0) + grid.addWidget(_hide_if_basic(invalidation_fee_box), 16, 1) + grid.addWidget(_hide_if_basic(help_invalidation_fee), 16, 2) + grid.addWidget(_hide_if_basic(btn_reset_invalidation_fee), 16, 3) + # ----------------------------------------------------------------- # # Group C / C4b: "Reset" button that restores the dialog settings to # # their factory defaults. It only resets the settings exposed by THIS # @@ -968,6 +1044,8 @@ class Plugin(BalPlugin, EventListener): # reset restores SAVE_HISTORY to its default, so the field must # follow the (default) checkbox state again. edit_history_label.setEnabled(bool(self.SAVE_HISTORY.get())) + # Reset the invalidation fee policy to its default ("eta:2") too. + reset_invalidation_fee() # Refresh the open BAL windows so any dependent view (e.g. the # editable-dates state is not in this list, but hide filters are) # reflects the reset values. diff --git a/bal/gui/qt/window.py b/bal/gui/qt/window.py index 57d1b38..8ab73bb 100644 --- a/bal/gui/qt/window.py +++ b/bal/gui/qt/window.py @@ -1089,6 +1089,7 @@ class BalWindow: will_locktime=Will.get_min_locktime( willitems, default_value=self.date_to_check ), + fee_policy_descriptor=self.bal_plugin.INVALIDATION_FEE_POLICY.get(), ) msg = _("Calculating Transactions") self.waiting_dialog = BalWaitingDialog( @@ -1424,6 +1425,7 @@ class BalWindow: willitems, default_value=getattr(self, "date_to_check", None), ), + fee_policy_descriptor=self.bal_plugin.INVALIDATION_FEE_POLICY.get(), ) except Exception as e: _logger.error("auto invalidate: could not build tx: {}".format(e)) diff --git a/bal/locale/bal.pot b/bal/locale/bal.pot index 277c7d0..529724f 100644 --- a/bal/locale/bal.pot +++ b/bal/locale/bal.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2026-10-07 14:25-0400\n" +"POT-Creation-Date: 2026-10-10 15:53-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,111 +17,110 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.10.3\n" -#: bal/core/plugin_base.py:360 +#: bal/core/plugin_base.py:378 msgid "" "BAL will execution of $wallet_name\r\n" " heirs list: \r\n" "$heirs_complete" msgstr "" -#: bal/core/plugin_base.py:366 +#: bal/core/plugin_base.py:384 msgid "BAL -Will execution of $wallet_name" msgstr "" -#: bal/core/will.py:1317 +#: bal/core/will.py:1502 msgid "NOT {}" msgstr "" -#: bal/core/will.py:1341 +#: bal/core/will.py:1526 msgid "Anticipated" msgstr "" -#: bal/core/will.py:1342 +#: bal/core/will.py:1527 msgid "Broadcasted" msgstr "" -#: bal/core/will.py:1343 +#: bal/core/will.py:1528 msgid "Checked" msgstr "" -#: bal/core/will.py:1344 +#: bal/core/will.py:1529 msgid "Check Failed" msgstr "" -#: bal/core/will.py:1345 +#: bal/core/will.py:1530 msgid "Signed" msgstr "" -#: bal/core/will.py:1346 +#: bal/core/will.py:1531 msgid "Confirmed" msgstr "" -#: bal/core/will.py:1347 bal/gui/qt/lists.py:1354 bal/gui/qt/lists.py:1368 +#: bal/core/will.py:1532 bal/gui/qt/lists.py:1354 bal/gui/qt/lists.py:1368 msgid "Error" msgstr "" -#: bal/core/will.py:1348 bal/gui/qt/dialogs.py:825 +#: bal/core/will.py:1533 bal/gui/qt/dialogs.py:826 msgid "Expired" msgstr "" -#: bal/core/will.py:1349 +#: bal/core/will.py:1534 msgid "Exported" msgstr "" -#: bal/core/will.py:1350 +#: bal/core/will.py:1535 msgid "Imported" msgstr "" -#: bal/core/will.py:1351 +#: bal/core/will.py:1536 msgid "Invalidated" msgstr "" -#: bal/core/will.py:1352 +#: bal/core/will.py:1537 msgid "Mempool" msgstr "" -#: bal/core/will.py:1353 +#: bal/core/will.py:1538 msgid "Push failed" msgstr "" -#: bal/core/will.py:1354 +#: bal/core/will.py:1539 msgid "Pushed" msgstr "" -#: bal/core/will.py:1355 +#: bal/core/will.py:1540 msgid "Partially Signed" msgstr "" -#: bal/core/will.py:1356 +#: bal/core/will.py:1541 msgid "Replaced" msgstr "" -#: bal/core/will.py:1357 +#: bal/core/will.py:1542 msgid "Restored" msgstr "" -#: bal/core/will.py:1358 +#: bal/core/will.py:1543 msgid "Updated" msgstr "" -#: bal/core/will.py:1359 bal/gui/qt/dialogs.py:2633 +#: bal/core/will.py:1544 bal/gui/qt/dialogs.py:2635 msgid "Valid" msgstr "" -#: bal/core/will.py:1365 bal/gui/qt/window.py:554 +#: bal/core/will.py:1550 msgid "New" msgstr "" -#: bal/core/willexecutors.py:245 +#: bal/core/willexecutors.py:260 msgid "Waiting..." msgstr "" -#: bal/core/willexecutors.py:379 bal/gui/qt/window.py:245 -#: bal/gui/qt/window.py:1663 +#: bal/core/willexecutors.py:394 bal/gui/qt/window.py:245 bal/gui/qt/window.py:1667 msgid "Success" msgstr "" -#: bal/core/willexecutors.py:389 bal/gui/qt/window.py:1668 +#: bal/core/willexecutors.py:404 bal/gui/qt/window.py:1672 msgid "Failed" msgstr "" @@ -137,7 +136,7 @@ msgstr "" msgid "Save" msgstr "" -#: bal/gui/qt/calendar.py:72 bal/gui/qt/widgets.py:1043 +#: bal/gui/qt/calendar.py:73 bal/gui/qt/widgets.py:1044 msgid "No reminders were saved: the delivery date is too close (or already passed)" msgstr "" @@ -195,15 +194,15 @@ msgstr "" msgid "Bal Wizard Setup" msgstr "" -#: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2762 +#: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2764 msgid "Previous" msgstr "" -#: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2765 +#: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2767 msgid "Next" msgstr "" -#: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4048 +#: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4050 msgid "Cancel" msgstr "" @@ -221,14 +220,11 @@ msgstr "" msgid "Add" msgstr "" -#: bal/gui/qt/dialogs.py:372 bal/gui/qt/dialogs.py:2594 bal/gui/qt/lists.py:311 -#: bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237 +#: bal/gui/qt/dialogs.py:372 bal/gui/qt/dialogs.py:2596 bal/gui/qt/lists.py:311 bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237 msgid "Import" msgstr "" -#: bal/gui/qt/dialogs.py:374 bal/gui/qt/dialogs.py:2430 -#: bal/gui/qt/dialogs.py:2598 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672 -#: bal/gui/qt/lists.py:1249 +#: bal/gui/qt/dialogs.py:374 bal/gui/qt/dialogs.py:2432 bal/gui/qt/dialogs.py:2600 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672 bal/gui/qt/lists.py:1249 msgid "Export" msgstr "" @@ -280,125 +276,117 @@ msgstr "" msgid "Building Will:" msgstr "" -#: bal/gui/qt/dialogs.py:731 bal/gui/qt/dialogs.py:756 -#: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:789 +#: bal/gui/qt/dialogs.py:731 bal/gui/qt/dialogs.py:757 bal/gui/qt/dialogs.py:767 bal/gui/qt/dialogs.py:790 msgid "Checking variables" msgstr "" -#: bal/gui/qt/dialogs.py:757 +#: bal/gui/qt/dialogs.py:758 msgid "Check Alive Threshold Passed: you have to Invalidate your old Will" msgstr "" -#: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:848 +#: bal/gui/qt/dialogs.py:767 bal/gui/qt/dialogs.py:850 msgid "No Heirs" msgstr "" -#: bal/gui/qt/dialogs.py:789 bal/gui/qt/dialogs.py:1336 -#: bal/gui/qt/dialogs.py:2282 bal/gui/qt/window.py:2315 +#: bal/gui/qt/dialogs.py:790 bal/gui/qt/dialogs.py:1338 bal/gui/qt/dialogs.py:2284 bal/gui/qt/window.py:2319 msgid "Ok" msgstr "" -#: bal/gui/qt/dialogs.py:793 +#: bal/gui/qt/dialogs.py:794 msgid "" "In the inheritance process, the entire wallet will always be fully emptied. \n" "Your settings require an adjustment of the amounts" msgstr "" -#: bal/gui/qt/dialogs.py:802 bal/gui/qt/window.py:844 +#: bal/gui/qt/dialogs.py:803 bal/gui/qt/window.py:846 msgid "Will-executor fee too high: {}" msgstr "" -#: bal/gui/qt/dialogs.py:836 +#: bal/gui/qt/dialogs.py:837 msgid "Postponed: invalidating old will" msgstr "" -#: bal/gui/qt/dialogs.py:871 bal/gui/qt/dialogs.py:880 -#: bal/gui/qt/dialogs.py:2242 +#: bal/gui/qt/dialogs.py:873 bal/gui/qt/dialogs.py:882 bal/gui/qt/dialogs.py:2244 msgid "Checking your will" msgstr "" -#: bal/gui/qt/dialogs.py:873 +#: bal/gui/qt/dialogs.py:875 msgid "An inheritance of this wallet is already executed (on blockchain)" msgstr "" -#: bal/gui/qt/dialogs.py:882 +#: bal/gui/qt/dialogs.py:884 msgid "Inheritance in mempool (waiting confirmation)" msgstr "" -#: bal/gui/qt/dialogs.py:915 +#: bal/gui/qt/dialogs.py:917 msgid "Will-Executor excluded" msgstr "" -#: bal/gui/qt/dialogs.py:915 bal/gui/qt/dialogs.py:1009 -#: bal/gui/qt/dialogs.py:2223 bal/gui/qt/dialogs.py:2234 +#: bal/gui/qt/dialogs.py:917 bal/gui/qt/dialogs.py:1011 bal/gui/qt/dialogs.py:2225 bal/gui/qt/dialogs.py:2236 msgid "Skipped" msgstr "" -#: bal/gui/qt/dialogs.py:921 bal/gui/qt/dialogs.py:1706 bal/gui/qt/lists.py:345 -#: bal/gui/qt/theme.py:116 +#: bal/gui/qt/dialogs.py:923 bal/gui/qt/dialogs.py:1708 bal/gui/qt/lists.py:345 bal/gui/qt/theme.py:116 msgid "Will-Executor" msgstr "" -#: bal/gui/qt/dialogs.py:922 +#: bal/gui/qt/dialogs.py:924 msgid "Not present - select one or enable backup mode" msgstr "" -#: bal/gui/qt/dialogs.py:983 +#: bal/gui/qt/dialogs.py:986 msgid "All heirs' shares are below the dust limit: the inheritance cannot be created. Increase the amounts or reduce the number of heirs." msgstr "" -#: bal/gui/qt/dialogs.py:999 +#: bal/gui/qt/dialogs.py:1002 msgid "Wallet balance is too low: {} satoshi available, but the miner and will-executor fees need {} satoshi (the minimum usable amount is {} satoshi). Add funds, or select fewer will-executors." msgstr "" -#: bal/gui/qt/dialogs.py:1043 +#: bal/gui/qt/dialogs.py:1045 msgid "Heir {}" msgstr "" -#: bal/gui/qt/dialogs.py:1045 +#: bal/gui/qt/dialogs.py:1047 msgid "{} is DUST - excluded (amount below dust limit)" msgstr "" -#: bal/gui/qt/dialogs.py:1101 +#: bal/gui/qt/dialogs.py:1103 msgid "Heir" msgstr "" -#: bal/gui/qt/dialogs.py:1218 bal/gui/qt/dialogs.py:2271 +#: bal/gui/qt/dialogs.py:1220 bal/gui/qt/dialogs.py:2273 msgid "Please wait {}secs" msgstr "" -#: bal/gui/qt/dialogs.py:1225 bal/gui/qt/dialogs.py:1277 -#: bal/gui/qt/dialogs.py:1315 +#: bal/gui/qt/dialogs.py:1227 bal/gui/qt/dialogs.py:1279 bal/gui/qt/dialogs.py:1317 msgid "Broadcasting" msgstr "" -#: bal/gui/qt/dialogs.py:1336 bal/gui/qt/window.py:2317 +#: bal/gui/qt/dialogs.py:1338 bal/gui/qt/window.py:2321 msgid "Ko" msgstr "" -#: bal/gui/qt/dialogs.py:1347 +#: bal/gui/qt/dialogs.py:1349 msgid "Timeout - no answer" msgstr "" -#: bal/gui/qt/dialogs.py:1389 +#: bal/gui/qt/dialogs.py:1391 msgid "checking {} - {}" msgstr "" -#: bal/gui/qt/dialogs.py:1392 bal/gui/qt/dialogs.py:2240 -#: bal/gui/qt/window.py:1691 +#: bal/gui/qt/dialogs.py:1394 bal/gui/qt/dialogs.py:2242 bal/gui/qt/window.py:1695 msgid "Waiting" msgstr "" -#: bal/gui/qt/dialogs.py:1406 +#: bal/gui/qt/dialogs.py:1408 msgid "checked {} - {}" msgstr "" -#: bal/gui/qt/dialogs.py:1456 bal/gui/qt/dialogs.py:2026 -#: bal/gui/qt/dialogs.py:2037 +#: bal/gui/qt/dialogs.py:1458 bal/gui/qt/dialogs.py:2028 bal/gui/qt/dialogs.py:2039 msgid "Error: {}" msgstr "" -#: bal/gui/qt/dialogs.py:1517 +#: bal/gui/qt/dialogs.py:1519 msgid "" "Your will has expired and must be invalidated before it can be rebuilt.\n" "A transaction window will now open:\n" @@ -406,159 +394,156 @@ msgid "" "After the invalidation is confirmed, press the Check button to finish the will." msgstr "" -#: bal/gui/qt/dialogs.py:1546 bal/gui/qt/window.py:849 +#: bal/gui/qt/dialogs.py:1549 bal/gui/qt/window.py:852 msgid "Check Alive must be after today and before the delivery date, please update it" msgstr "" -#: bal/gui/qt/dialogs.py:1569 +#: bal/gui/qt/dialogs.py:1571 msgid "" "Your old will has been invalidated and the transaction was broadcast.\n" "Electrum may need a little time to register it.\n" "Please wait until the invalidation transaction is confirmed, then press the Check button again to finish updating your will." msgstr "" -#: bal/gui/qt/dialogs.py:1585 +#: bal/gui/qt/dialogs.py:1587 msgid "Invalidate your old will" msgstr "" -#: bal/gui/qt/dialogs.py:1599 bal/gui/qt/dialogs.py:1644 +#: bal/gui/qt/dialogs.py:1601 bal/gui/qt/dialogs.py:1646 msgid "Aborted" msgstr "" -#: bal/gui/qt/dialogs.py:1618 +#: bal/gui/qt/dialogs.py:1620 msgid "Auto-sign disabled — sign manually" msgstr "" -#: bal/gui/qt/dialogs.py:1631 +#: bal/gui/qt/dialogs.py:1633 msgid "" "The delivery date was automatically moved one day earlier so the updated will can correctly replace the previous one.\n" "Please sign (and broadcast) to confirm the change." msgstr "" -#: bal/gui/qt/dialogs.py:1641 +#: bal/gui/qt/dialogs.py:1643 msgid "Sign your will" msgstr "" -#: bal/gui/qt/dialogs.py:1646 bal/gui/qt/dialogs.py:1929 +#: bal/gui/qt/dialogs.py:1648 bal/gui/qt/dialogs.py:1931 msgid "Nothing to do" msgstr "" -#: bal/gui/qt/dialogs.py:1665 +#: bal/gui/qt/dialogs.py:1667 msgid "Finished" msgstr "" -#: bal/gui/qt/dialogs.py:1682 +#: bal/gui/qt/dialogs.py:1684 msgid "Calendar" msgstr "" -#: bal/gui/qt/dialogs.py:1684 bal/gui/qt/dialogs.py:1714 -#: bal/gui/qt/dialogs.py:1781 bal/gui/qt/dialogs.py:2029 -#: bal/gui/qt/dialogs.py:2976 bal/gui/qt/dialogs.py:3231 -#: bal/gui/qt/dialogs.py:4067 +#: bal/gui/qt/dialogs.py:1686 bal/gui/qt/dialogs.py:1716 bal/gui/qt/dialogs.py:1783 bal/gui/qt/dialogs.py:2031 bal/gui/qt/dialogs.py:2978 bal/gui/qt/dialogs.py:3233 bal/gui/qt/dialogs.py:4069 msgid "Close" msgstr "" -#: bal/gui/qt/dialogs.py:1710 bal/gui/qt/dialogs.py:1777 +#: bal/gui/qt/dialogs.py:1712 bal/gui/qt/dialogs.py:1779 msgid "🔮 Wizard" msgstr "" -#: bal/gui/qt/dialogs.py:1773 bal/gui/qt/dialogs.py:2581 +#: bal/gui/qt/dialogs.py:1775 bal/gui/qt/dialogs.py:2583 msgid "Heirs" msgstr "" -#: bal/gui/qt/dialogs.py:1882 bal/gui/qt/widgets.py:1150 +#: bal/gui/qt/dialogs.py:1884 bal/gui/qt/widgets.py:1150 msgid "(reminder {idx}/{total})" msgstr "" -#: bal/gui/qt/dialogs.py:1946 +#: bal/gui/qt/dialogs.py:1948 msgid "All done" msgstr "" -#: bal/gui/qt/dialogs.py:1997 +#: bal/gui/qt/dialogs.py:2000 msgid "Next steps (manual): press 'Sign' to sign your will, then 'Broadcast' to send it to the will-executors." msgstr "" -#: bal/gui/qt/dialogs.py:2002 +#: bal/gui/qt/dialogs.py:2005 msgid "Next step (manual): press 'Sign' to sign your will." msgstr "" -#: bal/gui/qt/dialogs.py:2006 +#: bal/gui/qt/dialogs.py:2009 msgid "Next step (manual): press 'Broadcast' to send your will to the will-executors." msgstr "" -#: bal/gui/qt/dialogs.py:2101 +#: bal/gui/qt/dialogs.py:2104 msgid "Miner fee rate changed (the will was built with {} sat/byte, now it is {}): a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2106 +#: bal/gui/qt/dialogs.py:2109 msgid "The miner fee rate changed: a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2110 +#: bal/gui/qt/dialogs.py:2112 msgid "Will-executor \"{}\" is not covered by the current will: a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2115 +#: bal/gui/qt/dialogs.py:2118 msgid "Backup mode is enabled but the will has no backup transaction: a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2124 +#: bal/gui/qt/dialogs.py:2126 msgid "Heir \"{}\" is not covered by the current will (it was added or removed, or its delivery date changed): a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2133 +#: bal/gui/qt/dialogs.py:2135 msgid "The heirs changed: a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2135 +#: bal/gui/qt/dialogs.py:2137 msgid "A will-executor changed: a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2144 +#: bal/gui/qt/dialogs.py:2147 msgid "The wallet contains funds that the current will does not cover yet: a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2148 +#: bal/gui/qt/dialogs.py:2151 msgid "The will contains no valid transaction: a new will must be prepared." msgstr "" -#: bal/gui/qt/dialogs.py:2154 +#: bal/gui/qt/dialogs.py:2157 msgid "The will is no longer coherent and must be rebuilt; the exact reason could not be determined." msgstr "" -#: bal/gui/qt/dialogs.py:2184 +#: bal/gui/qt/dialogs.py:2187 msgid "No heirs: add at least one heir before building the will." msgstr "" -#: bal/gui/qt/dialogs.py:2187 +#: bal/gui/qt/dialogs.py:2190 msgid "The wallet has no spendable funds, so no inheritance transaction can be created." msgstr "" -#: bal/gui/qt/dialogs.py:2191 +#: bal/gui/qt/dialogs.py:2194 msgid "No usable will-executor: none of the servers in the list is both selected and valid. Open the will-executor settings, select at least one server and check that it is reachable." msgstr "" -#: bal/gui/qt/dialogs.py:2196 +#: bal/gui/qt/dialogs.py:2199 msgid "No delivery date left to build: every heir's date is already covered by the existing will. Choose a later delivery date, or change an heir's date." msgstr "" -#: bal/gui/qt/dialogs.py:2201 +#: bal/gui/qt/dialogs.py:2204 msgid "The amount to send must cover the miner fees plus this will-executor's fee, and the wallet balance is not enough: select cheaper will-executors, or add funds to the wallet." msgstr "" -#: bal/gui/qt/dialogs.py:2206 +#: bal/gui/qt/dialogs.py:2209 msgid "A will-executor asks for more than the maximum fee you allowed: raise the maximum fee in the settings, or select a cheaper will-executor." msgstr "" -#: bal/gui/qt/dialogs.py:2211 +#: bal/gui/qt/dialogs.py:2214 msgid "The inheritance transactions could not be assembled from the available funds (the balance may not cover the miner fees)." msgstr "" -#: bal/gui/qt/dialogs.py:2215 +#: bal/gui/qt/dialogs.py:2218 msgid "An unexpected error stopped the transactions being prepared for a will-executor, so it was skipped. Try again, or select a different will-executor." msgstr "" -#: bal/gui/qt/dialogs.py:2226 +#: bal/gui/qt/dialogs.py:2228 msgid "" "Could not build the will, and the exact cause could not be determined. Please check that:\n" "- the wallet balance covers the miner and will-executor fees,\n" @@ -567,403 +552,399 @@ msgid "" "- at least one will-executor is selected and reachable." msgstr "" -#: bal/gui/qt/dialogs.py:2247 +#: bal/gui/qt/dialogs.py:2249 msgid "Invalidating old will" msgstr "" -#: bal/gui/qt/dialogs.py:2253 +#: bal/gui/qt/dialogs.py:2255 msgid "Building your will" msgstr "" -#: bal/gui/qt/dialogs.py:2259 +#: bal/gui/qt/dialogs.py:2261 msgid "Signing your will" msgstr "" -#: bal/gui/qt/dialogs.py:2265 +#: bal/gui/qt/dialogs.py:2267 msgid "Broadcasting your will to executors" msgstr "" -#: bal/gui/qt/dialogs.py:2313 +#: bal/gui/qt/dialogs.py:2315 msgid "Wait" msgstr "" -#: bal/gui/qt/dialogs.py:2416 +#: bal/gui/qt/dialogs.py:2418 msgid "Will Details" msgstr "" -#: bal/gui/qt/dialogs.py:2422 bal/gui/qt/dialogs.py:4042 -#: bal/gui/qt/lists.py:668 +#: bal/gui/qt/dialogs.py:2424 bal/gui/qt/dialogs.py:4044 bal/gui/qt/lists.py:668 msgid "Sign" msgstr "" -#: bal/gui/qt/dialogs.py:2426 bal/gui/qt/lists.py:675 +#: bal/gui/qt/dialogs.py:2428 bal/gui/qt/lists.py:675 msgid "Broadcast" msgstr "" -#: bal/gui/qt/dialogs.py:2433 bal/gui/qt/lists.py:677 +#: bal/gui/qt/dialogs.py:2435 bal/gui/qt/lists.py:677 msgid "Invalidate" msgstr "" -#: bal/gui/qt/dialogs.py:2438 bal/gui/qt/lists.py:674 +#: bal/gui/qt/dialogs.py:2440 bal/gui/qt/lists.py:674 msgid "Merge" msgstr "" -#: bal/gui/qt/dialogs.py:2446 +#: bal/gui/qt/dialogs.py:2448 msgid "Expiration date: {}" msgstr "" -#: bal/gui/qt/dialogs.py:2452 +#: bal/gui/qt/dialogs.py:2454 msgid "Valid Txs:{}" msgstr "" -#: bal/gui/qt/dialogs.py:2454 +#: bal/gui/qt/dialogs.py:2456 msgid "Total Txs:{}" msgstr "" -#: bal/gui/qt/dialogs.py:2508 +#: bal/gui/qt/dialogs.py:2510 msgid "Hide replaced" msgstr "" -#: bal/gui/qt/dialogs.py:2510 +#: bal/gui/qt/dialogs.py:2512 msgid "Unhide replaced" msgstr "" -#: bal/gui/qt/dialogs.py:2516 +#: bal/gui/qt/dialogs.py:2518 msgid "Hide invalidated" msgstr "" -#: bal/gui/qt/dialogs.py:2518 +#: bal/gui/qt/dialogs.py:2520 msgid "Unhide invalidated" msgstr "" -#: bal/gui/qt/dialogs.py:2543 +#: bal/gui/qt/dialogs.py:2545 msgid "Will-Executor Service List" msgstr "" -#: bal/gui/qt/dialogs.py:2590 bal/gui/qt/lists.py:314 +#: bal/gui/qt/dialogs.py:2592 bal/gui/qt/lists.py:314 msgid "New Heir" msgstr "" -#: bal/gui/qt/dialogs.py:2632 +#: bal/gui/qt/dialogs.py:2634 msgid "All" msgstr "" -#: bal/gui/qt/dialogs.py:2635 +#: bal/gui/qt/dialogs.py:2637 msgid "Valid NC" msgstr "" -#: bal/gui/qt/dialogs.py:2773 bal/gui/qt/dialogs.py:2851 +#: bal/gui/qt/dialogs.py:2775 bal/gui/qt/dialogs.py:2853 msgid "Auto" msgstr "" -#: bal/gui/qt/dialogs.py:2775 +#: bal/gui/qt/dialogs.py:2777 msgid "Automatically advance through the QR codes." msgstr "" -#: bal/gui/qt/dialogs.py:2779 +#: bal/gui/qt/dialogs.py:2781 msgid "QR codes per second:" msgstr "" -#: bal/gui/qt/dialogs.py:2783 +#: bal/gui/qt/dialogs.py:2785 msgid " /s" msgstr "" -#: bal/gui/qt/dialogs.py:2785 +#: bal/gui/qt/dialogs.py:2787 msgid "Loop" msgstr "" -#: bal/gui/qt/dialogs.py:2787 +#: bal/gui/qt/dialogs.py:2790 msgid "When the last QR code is reached, keep cycling from the first one instead of stopping." msgstr "" -#: bal/gui/qt/dialogs.py:2797 +#: bal/gui/qt/dialogs.py:2799 msgid "QR code size:" msgstr "" -#: bal/gui/qt/dialogs.py:2845 +#: bal/gui/qt/dialogs.py:2847 msgid "Stop" msgstr "" -#: bal/gui/qt/dialogs.py:2866 +#: bal/gui/qt/dialogs.py:2868 msgid "" "Scan the QR codes below with the will-opening device.\n" "{} codes carry the whole transfer (any order works, duplicates are ignored)." msgstr "" -#: bal/gui/qt/dialogs.py:2888 +#: bal/gui/qt/dialogs.py:2890 msgid "Frame {} of {}" msgstr "" -#: bal/gui/qt/dialogs.py:2911 +#: bal/gui/qt/dialogs.py:2913 msgid "Export will" msgstr "" -#: bal/gui/qt/dialogs.py:2922 +#: bal/gui/qt/dialogs.py:2924 msgid "No will transaction to export." msgstr "" -#: bal/gui/qt/dialogs.py:2929 +#: bal/gui/qt/dialogs.py:2931 msgid "Export:" msgstr "" -#: bal/gui/qt/dialogs.py:2936 +#: bal/gui/qt/dialogs.py:2938 msgid "Whole will" msgstr "" -#: bal/gui/qt/dialogs.py:2939 +#: bal/gui/qt/dialogs.py:2942 msgid "Export the full will items (all details/statuses) as JSON, or only the serialized transactions." msgstr "" -#: bal/gui/qt/dialogs.py:2950 +#: bal/gui/qt/dialogs.py:2952 msgid "Send as:" msgstr "" -#: bal/gui/qt/dialogs.py:2952 bal/gui/qt/dialogs.py:3208 +#: bal/gui/qt/dialogs.py:2954 bal/gui/qt/dialogs.py:3210 msgid "File" msgstr "" -#: bal/gui/qt/dialogs.py:2953 bal/gui/qt/dialogs.py:3209 +#: bal/gui/qt/dialogs.py:2955 bal/gui/qt/dialogs.py:3211 msgid "QR Code" msgstr "" -#: bal/gui/qt/dialogs.py:2954 bal/gui/qt/dialogs.py:3210 +#: bal/gui/qt/dialogs.py:2956 bal/gui/qt/dialogs.py:3212 msgid "Audio" msgstr "" -#: bal/gui/qt/dialogs.py:3005 +#: bal/gui/qt/dialogs.py:3007 msgid "Export the will as a JSON file. Uncheck \"Whole will\" above to export only the serialized transactions (comma-separated)." msgstr "" -#: bal/gui/qt/dialogs.py:3014 +#: bal/gui/qt/dialogs.py:3016 msgid "Export…" msgstr "" -#: bal/gui/qt/dialogs.py:3032 bal/gui/qt/dialogs.py:3271 -#: bal/gui/qt/window.py:1787 bal/gui/qt/window.py:1800 +#: bal/gui/qt/dialogs.py:3034 bal/gui/qt/dialogs.py:3273 bal/gui/qt/window.py:1791 bal/gui/qt/window.py:1804 msgid "Audio MODEM plugin is not available." msgstr "" -#: bal/gui/qt/dialogs.py:3036 bal/gui/qt/dialogs.py:3275 +#: bal/gui/qt/dialogs.py:3038 bal/gui/qt/dialogs.py:3277 msgid "Speed (KB/sec):" msgstr "" -#: bal/gui/qt/dialogs.py:3047 +#: bal/gui/qt/dialogs.py:3049 msgid "Send" msgstr "" -#: bal/gui/qt/dialogs.py:3100 bal/gui/qt/dialogs.py:3134 -#: bal/gui/qt/dialogs.py:3159 +#: bal/gui/qt/dialogs.py:3102 bal/gui/qt/dialogs.py:3136 bal/gui/qt/dialogs.py:3161 msgid "No will transaction matches the selected filter." msgstr "" -#: bal/gui/qt/dialogs.py:3123 +#: bal/gui/qt/dialogs.py:3125 msgid "item(s)" msgstr "" -#: bal/gui/qt/dialogs.py:3123 +#: bal/gui/qt/dialogs.py:3125 msgid "transaction(s)" msgstr "" -#: bal/gui/qt/dialogs.py:3125 +#: bal/gui/qt/dialogs.py:3127 msgid "{} {} will be exported." msgstr "" -#: bal/gui/qt/dialogs.py:3128 +#: bal/gui/qt/dialogs.py:3130 msgid "{} {} will be sent." msgstr "" -#: bal/gui/qt/dialogs.py:3152 bal/gui/qt/dialogs.py:3155 -#: bal/gui/qt/dialogs.py:3311 bal/gui/qt/dialogs.py:3314 +#: bal/gui/qt/dialogs.py:3154 bal/gui/qt/dialogs.py:3157 bal/gui/qt/dialogs.py:3313 bal/gui/qt/dialogs.py:3316 msgid "Invalid audio speed." msgstr "" -#: bal/gui/qt/dialogs.py:3191 +#: bal/gui/qt/dialogs.py:3193 msgid "Import will" msgstr "" -#: bal/gui/qt/dialogs.py:3197 +#: bal/gui/qt/dialogs.py:3199 msgid "" "Choose how the will was exported: from a file, QR codes or audio.\n" "The import never touches the live will." msgstr "" -#: bal/gui/qt/dialogs.py:3206 +#: bal/gui/qt/dialogs.py:3208 msgid "Import from:" msgstr "" -#: bal/gui/qt/dialogs.py:3246 +#: bal/gui/qt/dialogs.py:3249 msgid "Import the JSON will file written by the Export ▶ File option. The will opens in a read-only preview window." msgstr "" -#: bal/gui/qt/dialogs.py:3253 +#: bal/gui/qt/dialogs.py:3255 msgid "Choose will file…" msgstr "" -#: bal/gui/qt/dialogs.py:3284 +#: bal/gui/qt/dialogs.py:3286 msgid "Waiting for the audio transfer…" msgstr "" -#: bal/gui/qt/dialogs.py:3287 +#: bal/gui/qt/dialogs.py:3289 msgid "Receive by audio…" msgstr "" -#: bal/gui/qt/dialogs.py:3328 +#: bal/gui/qt/dialogs.py:3330 msgid "Receiving…" msgstr "" -#: bal/gui/qt/dialogs.py:3348 +#: bal/gui/qt/dialogs.py:3350 msgid "No transaction data received." msgstr "" -#: bal/gui/qt/dialogs.py:3366 +#: bal/gui/qt/dialogs.py:3368 msgid "Waiting for audio ({:.1f} kbps)…" msgstr "" -#: bal/gui/qt/dialogs.py:3439 +#: bal/gui/qt/dialogs.py:3441 msgid "Could not parse a transferred will item: {}" msgstr "" -#: bal/gui/qt/dialogs.py:3459 +#: bal/gui/qt/dialogs.py:3461 msgid "Could not parse a transferred transaction: {}" msgstr "" -#: bal/gui/qt/dialogs.py:3470 +#: bal/gui/qt/dialogs.py:3473 msgid "The imported will contains no valid transaction in this wallet." msgstr "" -#: bal/gui/qt/dialogs.py:3481 +#: bal/gui/qt/dialogs.py:3484 msgid "{} imported transaction(s) are not valid in this wallet and were skipped." msgstr "" -#: bal/gui/qt/dialogs.py:3581 +#: bal/gui/qt/dialogs.py:3583 msgid "" "Show the will QR codes to the camera, one after another.\n" "After the first code, every new code is captured automatically.\n" "The first frame sets the total number of codes; duplicates are ignored." msgstr "" -#: bal/gui/qt/dialogs.py:3591 bal/gui/qt/dialogs.py:3769 +#: bal/gui/qt/dialogs.py:3593 bal/gui/qt/dialogs.py:3771 msgid "Waiting for the first frame…" msgstr "" -#: bal/gui/qt/dialogs.py:3608 +#: bal/gui/qt/dialogs.py:3610 msgid "…or paste/type the frame text here" msgstr "" -#: bal/gui/qt/dialogs.py:3612 +#: bal/gui/qt/dialogs.py:3614 msgid "Add frame" msgstr "" -#: bal/gui/qt/dialogs.py:3618 bal/gui/qt/dialogs.py:3861 +#: bal/gui/qt/dialogs.py:3620 bal/gui/qt/dialogs.py:3863 msgid "Scan with camera…" msgstr "" -#: bal/gui/qt/dialogs.py:3620 +#: bal/gui/qt/dialogs.py:3623 msgid "Start the live camera. While it runs, every new QR code shown to the camera is captured automatically." msgstr "" -#: bal/gui/qt/dialogs.py:3627 +#: bal/gui/qt/dialogs.py:3629 msgid "Reset" msgstr "" -#: bal/gui/qt/dialogs.py:3634 +#: bal/gui/qt/dialogs.py:3636 msgid "Review and Sign…" msgstr "" -#: bal/gui/qt/dialogs.py:3653 +#: bal/gui/qt/dialogs.py:3656 msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; scan the first code again." msgstr "" -#: bal/gui/qt/dialogs.py:3744 +#: bal/gui/qt/dialogs.py:3746 msgid "All {} frames stored." msgstr "" -#: bal/gui/qt/dialogs.py:3750 +#: bal/gui/qt/dialogs.py:3752 msgid "Stored {} of {} frames." msgstr "" -#: bal/gui/qt/dialogs.py:3815 +#: bal/gui/qt/dialogs.py:3817 msgid "Cannot start QR scanner, no usable camera found." msgstr "" -#: bal/gui/qt/dialogs.py:3820 +#: bal/gui/qt/dialogs.py:3822 msgid "Stop scanning" msgstr "" -#: bal/gui/qt/dialogs.py:3867 +#: bal/gui/qt/dialogs.py:3869 msgid "Camera error: {}" msgstr "" -#: bal/gui/qt/dialogs.py:3913 +#: bal/gui/qt/dialogs.py:3916 msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; show the first code again." msgstr "" -#: bal/gui/qt/dialogs.py:3957 +#: bal/gui/qt/dialogs.py:3959 msgid "The transferred will contains no transactions." msgstr "" -#: bal/gui/qt/dialogs.py:3986 +#: bal/gui/qt/dialogs.py:3988 msgid "Review and sign imported will" msgstr "" -#: bal/gui/qt/dialogs.py:3998 +#: bal/gui/qt/dialogs.py:4001 msgid "Enter your wallet password to sign the imported transactions." msgstr "" -#: bal/gui/qt/dialogs.py:4045 +#: bal/gui/qt/dialogs.py:4047 msgid "Skip" msgstr "" -#: bal/gui/qt/dialogs.py:4061 +#: bal/gui/qt/dialogs.py:4063 msgid "Save signed file…" msgstr "" -#: bal/gui/qt/dialogs.py:4064 +#: bal/gui/qt/dialogs.py:4066 msgid "Show signed QR…" msgstr "" -#: bal/gui/qt/dialogs.py:4088 +#: bal/gui/qt/dialogs.py:4090 msgid "Transaction {} of {}" msgstr "" -#: bal/gui/qt/dialogs.py:4090 +#: bal/gui/qt/dialogs.py:4092 msgid "TXID: {}" msgstr "" -#: bal/gui/qt/dialogs.py:4093 +#: bal/gui/qt/dialogs.py:4095 msgid "unknown" msgstr "" -#: bal/gui/qt/dialogs.py:4099 +#: bal/gui/qt/dialogs.py:4101 msgid "(no outputs)" msgstr "" -#: bal/gui/qt/dialogs.py:4113 +#: bal/gui/qt/dialogs.py:4115 msgid "unknown (partial transaction)" msgstr "" -#: bal/gui/qt/dialogs.py:4115 +#: bal/gui/qt/dialogs.py:4117 msgid "" "Total outputs: {}\n" "Fee: {}" msgstr "" -#: bal/gui/qt/dialogs.py:4126 +#: bal/gui/qt/dialogs.py:4128 msgid "" "Signed {} of {} transactions.\n" "\n" "Save a signed file to carry to the broadcast device, or show the signed transactions as QR codes." msgstr "" -#: bal/gui/qt/dialogs.py:4145 +#: bal/gui/qt/dialogs.py:4147 msgid "Could not sign the transaction: {}" msgstr "" -#: bal/gui/qt/dialogs.py:4164 +#: bal/gui/qt/dialogs.py:4166 msgid "Signed will saved." msgstr "" -#: bal/gui/qt/dialogs.py:4175 +#: bal/gui/qt/dialogs.py:4177 msgid "No signed transaction to show." msgstr "" @@ -971,8 +952,7 @@ msgstr "" msgid "Name" msgstr "" -#: bal/gui/qt/lists.py:135 bal/gui/qt/lists.py:1432 bal/gui/qt/widgets.py:1420 -#: bal/gui/qt/window.py:374 +#: bal/gui/qt/lists.py:135 bal/gui/qt/lists.py:1432 bal/gui/qt/widgets.py:1420 bal/gui/qt/window.py:374 msgid "Address" msgstr "" @@ -1176,8 +1156,7 @@ msgstr "" msgid "Edit: {}" msgstr "" -#: bal/gui/qt/lists.py:1290 bal/gui/qt/lists.py:1302 bal/gui/qt/lists.py:1316 -#: bal/gui/qt/lists.py:1476 +#: bal/gui/qt/lists.py:1290 bal/gui/qt/lists.py:1302 bal/gui/qt/lists.py:1316 bal/gui/qt/lists.py:1476 msgid "New Will Executor" msgstr "" @@ -1229,97 +1208,97 @@ msgstr "" msgid "No will-executor is selected" msgstr "" -#: bal/gui/qt/plugin.py:139 +#: bal/gui/qt/plugin.py:141 msgid "Electrum Plugins" msgstr "" -#: bal/gui/qt/plugin.py:306 +#: bal/gui/qt/plugin.py:308 msgid "Bitcoin After Life" msgstr "" -#: bal/gui/qt/plugin.py:425 bal/gui/qt/plugin.py:450 +#: bal/gui/qt/plugin.py:427 bal/gui/qt/plugin.py:452 msgid "Settings" msgstr "" -#: bal/gui/qt/plugin.py:551 +#: bal/gui/qt/plugin.py:553 msgid "BASIC" msgstr "" -#: bal/gui/qt/plugin.py:551 +#: bal/gui/qt/plugin.py:553 msgid "ADVANCED" msgstr "" -#: bal/gui/qt/plugin.py:571 +#: bal/gui/qt/plugin.py:573 msgid "Enable ADVANCED mode" msgstr "" -#: bal/gui/qt/plugin.py:572 +#: bal/gui/qt/plugin.py:574 msgid "Type 'at My Risk' to enable ADVANCED mode" msgstr "" -#: bal/gui/qt/plugin.py:641 +#: bal/gui/qt/plugin.py:645 bal/gui/qt/plugin.py:983 msgid "Reset to default" msgstr "" -#: bal/gui/qt/plugin.py:659 +#: bal/gui/qt/plugin.py:663 msgid "Rebroadcast transactions" msgstr "" -#: bal/gui/qt/plugin.py:670 +#: bal/gui/qt/plugin.py:674 msgid "Warning: change these settings only if you know what you are doing." msgstr "" -#: bal/gui/qt/plugin.py:685 +#: bal/gui/qt/plugin.py:689 msgid "Hide Replaced" msgstr "" -#: bal/gui/qt/plugin.py:688 +#: bal/gui/qt/plugin.py:692 msgid "Hide replaced transactions from will detail and list" msgstr "" -#: bal/gui/qt/plugin.py:693 +#: bal/gui/qt/plugin.py:697 msgid "Hide Invalidated" msgstr "" -#: bal/gui/qt/plugin.py:696 +#: bal/gui/qt/plugin.py:700 msgid "Hide invalidated transactions from will detail and list" msgstr "" -#: bal/gui/qt/plugin.py:699 +#: bal/gui/qt/plugin.py:703 msgid "Auto-sign on Check" msgstr "" -#: bal/gui/qt/plugin.py:700 +#: bal/gui/qt/plugin.py:702 msgid "" "When checking, automatically sign and broadcast the will transactions to their will-executors.\n" "The wallet password is requested only if the wallet is encrypted." msgstr "" -#: bal/gui/qt/plugin.py:715 +#: bal/gui/qt/plugin.py:719 msgid "Panel editable Date and Fee" msgstr "" -#: bal/gui/qt/plugin.py:718 +#: bal/gui/qt/plugin.py:720 msgid "" "When enabled, the delivery-time and check-alive date fields can be edited everywhere (toolbar / Heirs tab), not only in the will-building wizard.\n" "When disabled, those dates are display-only outside the wizard." msgstr "" -#: bal/gui/qt/plugin.py:730 +#: bal/gui/qt/plugin.py:734 msgid "Max Will-Executor Fee (satoshi)" msgstr "" -#: bal/gui/qt/plugin.py:733 +#: bal/gui/qt/plugin.py:735 msgid "" "Maximum fee (in satoshi) allowed to be paid to a single will-executor. If a will-executor charges more than this, the will will not be built.\n" "Default: 500,000 satoshi (0.005 BTC)." msgstr "" -#: bal/gui/qt/plugin.py:745 +#: bal/gui/qt/plugin.py:749 msgid "User Type" msgstr "" -#: bal/gui/qt/plugin.py:748 +#: bal/gui/qt/plugin.py:750 msgid "" "Choose how much detail the plugin shows.\n" "\n" @@ -1328,19 +1307,19 @@ msgid "" "ADVANCED: shows every control, including the 'Check Alive' field and the Raw/Date selector. Allows configuring the welist server URL and keeps all date/fee fields always editable." msgstr "" -#: bal/gui/qt/plugin.py:762 +#: bal/gui/qt/plugin.py:766 msgid "Number of reminders" msgstr "" -#: bal/gui/qt/plugin.py:763 +#: bal/gui/qt/plugin.py:768 msgid "How many reminder alarms the exported calendar (.ics) event contains. Range: 1 to 5 (default 3). Only used in ADVANCED mode." msgstr "" -#: bal/gui/qt/plugin.py:773 +#: bal/gui/qt/plugin.py:777 msgid "Event summary" msgstr "" -#: bal/gui/qt/plugin.py:774 +#: bal/gui/qt/plugin.py:776 msgid "" "Default message to be used in event summary\n" "Variables:\n" @@ -1349,11 +1328,11 @@ msgid "" "Only used in ADVANCED mode." msgstr "" -#: bal/gui/qt/plugin.py:787 +#: bal/gui/qt/plugin.py:791 msgid "Event description" msgstr "" -#: bal/gui/qt/plugin.py:788 +#: bal/gui/qt/plugin.py:790 msgid "" "Default message to be used in event description\n" "Variables:\n" @@ -1362,30 +1341,30 @@ msgid "" "Only used in ADVANCED mode." msgstr "" -#: bal/gui/qt/plugin.py:802 +#: bal/gui/qt/plugin.py:806 msgid "Welist Server URL" msgstr "" -#: bal/gui/qt/plugin.py:803 +#: bal/gui/qt/plugin.py:808 msgid "URL of the server that provides the will-executor list. Only available in ADVANCED mode." msgstr "" -#: bal/gui/qt/plugin.py:813 +#: bal/gui/qt/plugin.py:817 msgid "Calendar app command" msgstr "" -#: bal/gui/qt/plugin.py:814 +#: bal/gui/qt/plugin.py:816 msgid "" "Command used to open .ics calendar files.\n" "Leave empty to use the system default (xdg-open/open/start).\n" "Only used in ADVANCED mode." msgstr "" -#: bal/gui/qt/plugin.py:828 +#: bal/gui/qt/plugin.py:832 msgid "Save inheritance transactions in history" msgstr "" -#: bal/gui/qt/plugin.py:829 +#: bal/gui/qt/plugin.py:831 msgid "" "After each check, save the valid will transactions into the wallet's local history (the History tab), each with a label.\n" "The label may contain the variable:\n" @@ -1393,11 +1372,11 @@ msgid "" "Only used in ADVANCED mode." msgstr "" -#: bal/gui/qt/plugin.py:842 +#: bal/gui/qt/plugin.py:846 msgid "History label" msgstr "" -#: bal/gui/qt/plugin.py:843 +#: bal/gui/qt/plugin.py:845 msgid "" "Label applied to the will transactions saved into the wallet's local history.\n" "Variables:\n" @@ -1405,48 +1384,63 @@ msgid "" "Only used in ADVANCED mode." msgstr "" -#: bal/gui/qt/plugin.py:866 +#: bal/gui/qt/plugin.py:870 msgid "Broadcast all transactions to willexecutors including those already pushed" msgstr "" -#: bal/gui/qt/plugin.py:875 +#: bal/gui/qt/plugin.py:879 msgid "Rebuild will on wallet close" msgstr "" -#: bal/gui/qt/plugin.py:876 +#: bal/gui/qt/plugin.py:878 msgid "" "Run the 'Build your will' wizard every time the wallet is closed or Electrum is quit, so the will is rebuilt and re-validated.\n" "When disabled, the will is only rebuilt when you press Check or Prepare. The last built state is still saved to the wallet." msgstr "" -#: bal/gui/qt/plugin.py:893 +#: bal/gui/qt/plugin.py:897 msgid "Rebuild automatically on new transactions" msgstr "" -#: bal/gui/qt/plugin.py:894 +#: bal/gui/qt/plugin.py:896 msgid "" "When a new transaction arrives for the wallet, automatically rebuild the will the same way the wizard does at wallet close: the delivery date is anticipated by one day so the new will replaces the previous one, and the rebuilt transactions are signed and sent to their will-executors.\n" "An on-chain invalidation transaction is only built when the anticipated delivery date would fall before the Check Alive threshold, or when the threshold is already in the past.\n" "When disabled (default), the will is only rebuilt on Check / Prepare / wallet close." msgstr "" -#: bal/gui/qt/plugin.py:976 +#: bal/gui/qt/plugin.py:970 +msgid "Invalidation fee policy" +msgstr "" + +#: bal/gui/qt/plugin.py:966 +msgid "" +"Fee rate the on-chain invalidation transaction pays.\n" +"The invalidation is sent TODAY (it spends the will's coins so the old inheritance transactions become unusable), so it should pay the current network fee - not the will's mining fee rate.\n" +"\n" +"Feerate: a fixed rate (sat/vByte).\n" +"ETA: fee based on the estimated confirmation time (blocks).\n" +"Mempool: fee targeting a depth in the memory pool.\n" +"Only used in ADVANCED mode." +msgstr "" + +#: bal/gui/qt/plugin.py:1054 msgid "Reset to Default Setting" msgstr "" -#: bal/gui/qt/plugin.py:977 +#: bal/gui/qt/plugin.py:1055 msgid "Reset these settings to their default values" msgstr "" -#: bal/gui/qt/plugin.py:984 +#: bal/gui/qt/plugin.py:1062 msgid "Open the Bitcoin After Life support website" msgstr "" -#: bal/gui/qt/plugin.py:996 +#: bal/gui/qt/plugin.py:1074 msgid "Support:" msgstr "" -#: bal/gui/qt/plugin.py:1053 +#: bal/gui/qt/plugin.py:1131 msgid "BAL - {}" msgstr "" @@ -1507,7 +1501,7 @@ msgstr "" msgid "Date" msgstr "" -#: bal/gui/qt/widgets.py:645 +#: bal/gui/qt/widgets.py:646 msgid "CHECK ALIVE

In DATA mode:
set the date for the “check alive” parameter.
When you open the wallet, if the “check alive” date has passed, the plugin will ask if you want to postpone the inheritance, since it assumes you still have control of the wallet and that you are still alive.

In RAW mode:
When less than this time is missing, ask to invalidate.
If you fail to invalidate during this time, your transactions will be delivered to your heirs.

if you choose Raw, you can insert various options based on suffix:
- d: number of days after current day(ex: 1d means tomorrow)
- y: number of years after current day(ex: 1y means one year from today)
The sign selects the reference date:
- a NEGATIVE value (ex: -30d) counts backwards from the delivery date (30 days before the delivery);
- a POSITIVE value (ex: 30d) counts forward from the inheritance creation date (30 days after the will was created).

Note: the date/time is expressed in your computer's Local time (not UTC time).
" msgstr "" @@ -1515,7 +1509,7 @@ msgstr "" msgid "Check Alive" msgstr "" -#: bal/gui/qt/widgets.py:686 +#: bal/gui/qt/widgets.py:687 msgid "DELIVERY TIME

Set Locktime for transactions.
Any time is needed transaction will be anticipated by 1day

(ONLY IN ADVANCED MODE)
if you choose Raw, you can insert various options based on suffix:
- d: number of days after current day(ex: 1d means tomorrow)
- y: number of years after currrent day(ex: 1y means one year from today)

Note: the date/time is expressed in your computer's Local time (not UTC time).
" msgstr "" @@ -1607,27 +1601,27 @@ msgstr "" msgid "heirs" msgstr "" -#: bal/gui/qt/window.py:720 +#: bal/gui/qt/window.py:722 msgid "Heirs are not defined" msgstr "" -#: bal/gui/qt/window.py:838 +#: bal/gui/qt/window.py:841 msgid "In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.{}" msgstr "" -#: bal/gui/qt/window.py:856 +#: bal/gui/qt/window.py:859 msgid "CheckAlive is in the past please update it to a date in the future but less than locktime" msgstr "" -#: bal/gui/qt/window.py:862 +#: bal/gui/qt/window.py:864 msgid "locktime is lower than threshold" msgstr "" -#: bal/gui/qt/window.py:876 +#: bal/gui/qt/window.py:878 msgid " no backup transaction or willexecutor selected" msgstr "" -#: bal/gui/qt/window.py:915 +#: bal/gui/qt/window.py:917 msgid "" "This inheritance was already signed/sent to will-executors and you are postponing it.\n" "\n" @@ -1636,43 +1630,43 @@ msgid "" "Please sign and broadcast the invalidation transaction now, then press 'Prepare' again to create the new (postponed) inheritance." msgstr "" -#: bal/gui/qt/window.py:937 +#: bal/gui/qt/window.py:939 msgid "Heirs changed:" msgstr "" -#: bal/gui/qt/window.py:939 +#: bal/gui/qt/window.py:941 msgid "Will-Executor not present:" msgstr "" -#: bal/gui/qt/window.py:941 +#: bal/gui/qt/window.py:943 msgid "Will-Executor changed" msgstr "" -#: bal/gui/qt/window.py:943 +#: bal/gui/qt/window.py:945 msgid "Txfees are changed" msgstr "" -#: bal/gui/qt/window.py:950 +#: bal/gui/qt/window.py:952 msgid "" "Found CHANGES to the DATE or the HEIRS,\n" "a NEW WILL must be prepared." msgstr "" -#: bal/gui/qt/window.py:957 +#: bal/gui/qt/window.py:959 msgid "will have to be built" msgstr "" -#: bal/gui/qt/window.py:977 +#: bal/gui/qt/window.py:979 msgid "" "Error:{}\n" " {}" msgstr "" -#: bal/gui/qt/window.py:979 +#: bal/gui/qt/window.py:981 msgid "Please, check your heirs, locktime and threshold!" msgstr "" -#: bal/gui/qt/window.py:993 +#: bal/gui/qt/window.py:995 msgid "" "Your inheritance has been rebuilt and now needs to be signed again.\n" "\n" @@ -1680,7 +1674,7 @@ msgid "" " 1. Press 'Sign' to sign the new transaction." msgstr "" -#: bal/gui/qt/window.py:1000 +#: bal/gui/qt/window.py:1002 msgid "" "Your inheritance has been rebuilt and now needs to be signed and re-sent to the will-executors.\n" "\n" @@ -1689,113 +1683,112 @@ msgid "" " 2. Press 'Broadcast' to send it to the will-executors." msgstr "" -#: bal/gui/qt/window.py:1038 +#: bal/gui/qt/window.py:1040 msgid "Electrum was unable to deserialize the transaction:" msgstr "" -#: bal/gui/qt/window.py:1052 +#: bal/gui/qt/window.py:1054 msgid "no tx" msgstr "" -#: bal/gui/qt/window.py:1066 +#: bal/gui/qt/window.py:1069 msgid "Please sign and broadcast this transaction to invalidate current will" msgstr "" -#: bal/gui/qt/window.py:1074 +#: bal/gui/qt/window.py:1076 msgid "No transactions to invalidate" msgstr "" -#: bal/gui/qt/window.py:1091 +#: bal/gui/qt/window.py:1094 msgid "Calculating Transactions" msgstr "" -#: bal/gui/qt/window.py:1107 +#: bal/gui/qt/window.py:1110 msgid "signed: {}" msgstr "" -#: bal/gui/qt/window.py:1108 +#: bal/gui/qt/window.py:1111 msgid "signing: {}" msgstr "" -#: bal/gui/qt/window.py:1584 +#: bal/gui/qt/window.py:1588 msgid "Signing transactions..." msgstr "" -#: bal/gui/qt/window.py:1598 +#: bal/gui/qt/window.py:1602 msgid "Some transaction was not broadcasted" msgstr "" -#: bal/gui/qt/window.py:1602 +#: bal/gui/qt/window.py:1606 msgid "All transactions are broadcasted to respective Will-Executors" msgstr "" -#: bal/gui/qt/window.py:1621 +#: bal/gui/qt/window.py:1625 msgid "Selecting Will-Executors" msgstr "" -#: bal/gui/qt/window.py:1636 +#: bal/gui/qt/window.py:1640 msgid "Broadcasting Transactions to Will-Executors:" msgstr "" -#: bal/gui/qt/window.py:1643 bal/gui/qt/window.py:2319 +#: bal/gui/qt/window.py:1647 bal/gui/qt/window.py:2323 msgid "waiting..." msgstr "" -#: bal/gui/qt/window.py:1659 +#: bal/gui/qt/window.py:1663 msgid "checking..." msgstr "" -#: bal/gui/qt/window.py:1690 +#: bal/gui/qt/window.py:1694 msgid "checking {} - {} : {}" msgstr "" -#: bal/gui/qt/window.py:1699 +#: bal/gui/qt/window.py:1703 msgid "checked {} - {} : {}" msgstr "" -#: bal/gui/qt/window.py:1895 bal/gui/qt/window.py:1933 -#: bal/gui/qt/window.py:1957 +#: bal/gui/qt/window.py:1899 bal/gui/qt/window.py:1937 bal/gui/qt/window.py:1961 msgid "Invalid will file: {}" msgstr "" -#: bal/gui/qt/window.py:1914 bal/gui/qt/window.py:1965 +#: bal/gui/qt/window.py:1918 bal/gui/qt/window.py:1969 msgid "will" msgstr "" -#: bal/gui/qt/window.py:2010 +#: bal/gui/qt/window.py:2014 msgid "Checking transactions" msgstr "" -#: bal/gui/qt/window.py:2059 +#: bal/gui/qt/window.py:2063 msgid "Check Transaction" msgstr "" -#: bal/gui/qt/window.py:2168 +#: bal/gui/qt/window.py:2172 msgid "" "Could not download the will-executors list.\n" "\n" "This is usually caused by your internet connection or a firewall, not by the plugin. Please check your connection (a VPN often helps) and try again." msgstr "" -#: bal/gui/qt/window.py:2178 +#: bal/gui/qt/window.py:2182 msgid "" "Could not download the will-executors list over Tor.\n" "\n" "Electrum is connected through Tor and the connection is taking too long. Your Tor connection may be slow. Please try again, or use a VPN (or temporarily disable Tor) for a faster connection." msgstr "" -#: bal/gui/qt/window.py:2189 +#: bal/gui/qt/window.py:2193 msgid "Downloading will-executors list..." msgstr "" -#: bal/gui/qt/window.py:2250 +#: bal/gui/qt/window.py:2254 msgid "" "No active will-executor servers found for the {chain} network.\n" "\n" "The welist server at {url} responded but returned no will-executors for this chain." msgstr "" -#: bal/gui/qt/window.py:2258 +#: bal/gui/qt/window.py:2262 msgid "" "Could not reach the configured welist server.\n" "\n" @@ -1805,15 +1798,15 @@ msgid "" "Please verify the welist server URL in the plugin settings." msgstr "" -#: bal/gui/qt/window.py:2268 +#: bal/gui/qt/window.py:2273 msgid "No active will-executor found for the {chain} network." msgstr "" -#: bal/gui/qt/window.py:2305 +#: bal/gui/qt/window.py:2309 msgid "Ping Will-Executors:" msgstr "" -#: bal/gui/qt/window.py:2365 +#: bal/gui/qt/window.py:2369 msgid "Ping Will-Executors" msgstr "" diff --git a/bal/locale/es_ES/LC_MESSAGES/bal.po b/bal/locale/es_ES/LC_MESSAGES/bal.po index 4578480..a647566 100644 --- a/bal/locale/es_ES/LC_MESSAGES/bal.po +++ b/bal/locale/es_ES/LC_MESSAGES/bal.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: BAL Electrum plugin VERSION\n" "Report-Msgid-Bugs-To: info@bitcoin-after.life\n" -"POT-Creation-Date: 2026-10-07 14:25-0400\n" +"POT-Creation-Date: 2026-10-10 15:53-0400\n" "PO-Revision-Date: 2026-10-03 11:40-0400\n" "Last-Translator: FULL NAME \n" "Language: es_ES\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.10.3\n" -#: bal/core/plugin_base.py:360 +#: bal/core/plugin_base.py:378 msgid "" "BAL will execution of $wallet_name\r\n" " heirs list: \r\n" @@ -28,105 +28,104 @@ msgstr "" " lista de herederos: \r\n" "$heirs_complete" -#: bal/core/plugin_base.py:366 +#: bal/core/plugin_base.py:384 msgid "BAL -Will execution of $wallet_name" msgstr "BAL - Ejecución del testamento de $wallet_name" -#: bal/core/will.py:1317 +#: bal/core/will.py:1502 #, python-brace-format msgid "NOT {}" msgstr "NO {}" -#: bal/core/will.py:1341 +#: bal/core/will.py:1526 msgid "Anticipated" msgstr "Anticipado" -#: bal/core/will.py:1342 +#: bal/core/will.py:1527 msgid "Broadcasted" msgstr "Transmitido" -#: bal/core/will.py:1343 +#: bal/core/will.py:1528 msgid "Checked" msgstr "Comprobado" -#: bal/core/will.py:1344 +#: bal/core/will.py:1529 msgid "Check Failed" msgstr "Comprobación fallida" -#: bal/core/will.py:1345 +#: bal/core/will.py:1530 msgid "Signed" msgstr "Firmado" -#: bal/core/will.py:1346 +#: bal/core/will.py:1531 msgid "Confirmed" msgstr "Confirmado" -#: bal/core/will.py:1347 bal/gui/qt/lists.py:1354 bal/gui/qt/lists.py:1368 +#: bal/core/will.py:1532 bal/gui/qt/lists.py:1354 bal/gui/qt/lists.py:1368 msgid "Error" msgstr "Error" -#: bal/core/will.py:1348 bal/gui/qt/dialogs.py:825 +#: bal/core/will.py:1533 bal/gui/qt/dialogs.py:826 msgid "Expired" msgstr "Caducado" -#: bal/core/will.py:1349 +#: bal/core/will.py:1534 msgid "Exported" msgstr "Exportado" -#: bal/core/will.py:1350 +#: bal/core/will.py:1535 msgid "Imported" msgstr "Importado" -#: bal/core/will.py:1351 +#: bal/core/will.py:1536 msgid "Invalidated" msgstr "Invalidado" -#: bal/core/will.py:1352 +#: bal/core/will.py:1537 msgid "Mempool" msgstr "Mempool" -#: bal/core/will.py:1353 +#: bal/core/will.py:1538 msgid "Push failed" msgstr "Envío fallido" -#: bal/core/will.py:1354 +#: bal/core/will.py:1539 msgid "Pushed" msgstr "Enviado" -#: bal/core/will.py:1355 +#: bal/core/will.py:1540 msgid "Partially Signed" msgstr "Firmado parcialmente" -#: bal/core/will.py:1356 +#: bal/core/will.py:1541 msgid "Replaced" msgstr "Sustituido" -#: bal/core/will.py:1357 +#: bal/core/will.py:1542 msgid "Restored" msgstr "Restaurado" -#: bal/core/will.py:1358 +#: bal/core/will.py:1543 msgid "Updated" msgstr "Actualizado" -#: bal/core/will.py:1359 bal/gui/qt/dialogs.py:2633 +#: bal/core/will.py:1544 bal/gui/qt/dialogs.py:2635 msgid "Valid" msgstr "Válido" -#: bal/core/will.py:1365 bal/gui/qt/window.py:554 +#: bal/core/will.py:1550 msgid "New" msgstr "Nuevo" -#: bal/core/willexecutors.py:245 +#: bal/core/willexecutors.py:260 msgid "Waiting..." msgstr "Esperando..." -#: bal/core/willexecutors.py:379 bal/gui/qt/window.py:245 -#: bal/gui/qt/window.py:1663 +#: bal/core/willexecutors.py:394 bal/gui/qt/window.py:245 bal/gui/qt/window.py:1667 msgid "Success" msgstr "Completado" -#: bal/core/willexecutors.py:389 bal/gui/qt/window.py:1668 +#: bal/core/willexecutors.py:404 bal/gui/qt/window.py:1672 msgid "Failed" msgstr "Fallido" @@ -142,7 +141,7 @@ msgstr "Abrir con..." msgid "Save" msgstr "Guardar" -#: bal/gui/qt/calendar.py:72 bal/gui/qt/widgets.py:1043 +#: bal/gui/qt/calendar.py:73 bal/gui/qt/widgets.py:1044 msgid "No reminders were saved: the delivery date is too close (or already passed)" msgstr "No se guardó ningún recordatorio: la fecha de entrega está demasiado cerca (o ya ha pasado)" @@ -207,15 +206,15 @@ msgstr "Sus {0} se exportaron a '{1}'" msgid "Bal Wizard Setup" msgstr "Configuración del asistente de BAL" -#: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2762 +#: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2764 msgid "Previous" msgstr "Anterior" -#: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2765 +#: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2767 msgid "Next" msgstr "Siguiente" -#: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4048 +#: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4050 msgid "Cancel" msgstr "Cancelar" @@ -235,14 +234,11 @@ msgstr "" msgid "Add" msgstr "Añadir" -#: bal/gui/qt/dialogs.py:372 bal/gui/qt/dialogs.py:2594 bal/gui/qt/lists.py:311 -#: bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237 +#: bal/gui/qt/dialogs.py:372 bal/gui/qt/dialogs.py:2596 bal/gui/qt/lists.py:311 bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237 msgid "Import" msgstr "Importar" -#: bal/gui/qt/dialogs.py:374 bal/gui/qt/dialogs.py:2430 -#: bal/gui/qt/dialogs.py:2598 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672 -#: bal/gui/qt/lists.py:1249 +#: bal/gui/qt/dialogs.py:374 bal/gui/qt/dialogs.py:2432 bal/gui/qt/dialogs.py:2600 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672 bal/gui/qt/lists.py:1249 msgid "Export" msgstr "Exportar" @@ -294,25 +290,23 @@ msgstr "Creación del testamento" msgid "Building Will:" msgstr "Creación del testamento:" -#: bal/gui/qt/dialogs.py:731 bal/gui/qt/dialogs.py:756 -#: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:789 +#: bal/gui/qt/dialogs.py:731 bal/gui/qt/dialogs.py:757 bal/gui/qt/dialogs.py:767 bal/gui/qt/dialogs.py:790 msgid "Checking variables" msgstr "Comprobando variables" -#: bal/gui/qt/dialogs.py:757 +#: bal/gui/qt/dialogs.py:758 msgid "Check Alive Threshold Passed: you have to Invalidate your old Will" msgstr "El umbral de verificación de vida se ha superado: debes invalidar tu antiguo testamento" -#: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:848 +#: bal/gui/qt/dialogs.py:767 bal/gui/qt/dialogs.py:850 msgid "No Heirs" msgstr "Sin herederos" -#: bal/gui/qt/dialogs.py:789 bal/gui/qt/dialogs.py:1336 -#: bal/gui/qt/dialogs.py:2282 bal/gui/qt/window.py:2315 +#: bal/gui/qt/dialogs.py:790 bal/gui/qt/dialogs.py:1338 bal/gui/qt/dialogs.py:2284 bal/gui/qt/window.py:2319 msgid "Ok" msgstr "Aceptar" -#: bal/gui/qt/dialogs.py:793 +#: bal/gui/qt/dialogs.py:794 msgid "" "In the inheritance process, the entire wallet will always be fully emptied. \n" "Your settings require an adjustment of the amounts" @@ -320,109 +314,103 @@ msgstr "" "En el proceso de herencia, la cartera siempre se vaciará por completo. \n" "Tu configuración requiere un ajuste de los importes" -#: bal/gui/qt/dialogs.py:802 bal/gui/qt/window.py:844 +#: bal/gui/qt/dialogs.py:803 bal/gui/qt/window.py:846 #, python-brace-format msgid "Will-executor fee too high: {}" msgstr "Comisión del Will-Executor demasiado alta: {}" -#: bal/gui/qt/dialogs.py:836 +#: bal/gui/qt/dialogs.py:837 msgid "Postponed: invalidating old will" msgstr "Aplazado: invalidando el antiguo testamento" -#: bal/gui/qt/dialogs.py:871 bal/gui/qt/dialogs.py:880 -#: bal/gui/qt/dialogs.py:2242 +#: bal/gui/qt/dialogs.py:873 bal/gui/qt/dialogs.py:882 bal/gui/qt/dialogs.py:2244 msgid "Checking your will" msgstr "Comprobando tu testamento" -#: bal/gui/qt/dialogs.py:873 +#: bal/gui/qt/dialogs.py:875 msgid "An inheritance of this wallet is already executed (on blockchain)" msgstr "Una herencia de esta cartera ya se ha ejecutado (en la blockchain)" -#: bal/gui/qt/dialogs.py:882 +#: bal/gui/qt/dialogs.py:884 msgid "Inheritance in mempool (waiting confirmation)" msgstr "Herencia en la mempool (esperando confirmación)" -#: bal/gui/qt/dialogs.py:915 +#: bal/gui/qt/dialogs.py:917 msgid "Will-Executor excluded" msgstr "Will-Executor excluido" -#: bal/gui/qt/dialogs.py:915 bal/gui/qt/dialogs.py:1009 -#: bal/gui/qt/dialogs.py:2223 bal/gui/qt/dialogs.py:2234 +#: bal/gui/qt/dialogs.py:917 bal/gui/qt/dialogs.py:1011 bal/gui/qt/dialogs.py:2225 bal/gui/qt/dialogs.py:2236 msgid "Skipped" msgstr "Omitido" -#: bal/gui/qt/dialogs.py:921 bal/gui/qt/dialogs.py:1706 bal/gui/qt/lists.py:345 -#: bal/gui/qt/theme.py:116 +#: bal/gui/qt/dialogs.py:923 bal/gui/qt/dialogs.py:1708 bal/gui/qt/lists.py:345 bal/gui/qt/theme.py:116 msgid "Will-Executor" msgstr "Will-Executor" -#: bal/gui/qt/dialogs.py:922 +#: bal/gui/qt/dialogs.py:924 msgid "Not present - select one or enable backup mode" msgstr "No presente - selecciona uno o activa el modo de copia de seguridad" -#: bal/gui/qt/dialogs.py:983 +#: bal/gui/qt/dialogs.py:986 msgid "All heirs' shares are below the dust limit: the inheritance cannot be created. Increase the amounts or reduce the number of heirs." msgstr "Todas las partes de los herederos están por debajo del límite de polvo (dust): no se puede crear la herencia. Aumenta los importes o reduce el número de herederos." -#: bal/gui/qt/dialogs.py:999 +#: bal/gui/qt/dialogs.py:1002 #, python-brace-format msgid "Wallet balance is too low: {} satoshi available, but the miner and will-executor fees need {} satoshi (the minimum usable amount is {} satoshi). Add funds, or select fewer will-executors." msgstr "El saldo de la cartera es demasiado bajo: {} satoshis disponibles, pero las comisiones de minería y del Will-Executor necesitan {} satoshis (el importe mínimo utilizable es {} satoshis). Añade fondos o selecciona menos Will-Executor." -#: bal/gui/qt/dialogs.py:1043 +#: bal/gui/qt/dialogs.py:1045 #, python-brace-format msgid "Heir {}" msgstr "Heredero {}" -#: bal/gui/qt/dialogs.py:1045 +#: bal/gui/qt/dialogs.py:1047 #, python-brace-format msgid "{} is DUST - excluded (amount below dust limit)" msgstr "{} es POLVO (dust) - excluido (importe por debajo del límite de polvo)" -#: bal/gui/qt/dialogs.py:1101 +#: bal/gui/qt/dialogs.py:1103 msgid "Heir" msgstr "Heredero" -#: bal/gui/qt/dialogs.py:1218 bal/gui/qt/dialogs.py:2271 +#: bal/gui/qt/dialogs.py:1220 bal/gui/qt/dialogs.py:2273 #, python-brace-format msgid "Please wait {}secs" msgstr "Espera {} segundos" -#: bal/gui/qt/dialogs.py:1225 bal/gui/qt/dialogs.py:1277 -#: bal/gui/qt/dialogs.py:1315 +#: bal/gui/qt/dialogs.py:1227 bal/gui/qt/dialogs.py:1279 bal/gui/qt/dialogs.py:1317 msgid "Broadcasting" msgstr "Transmitiendo" -#: bal/gui/qt/dialogs.py:1336 bal/gui/qt/window.py:2317 +#: bal/gui/qt/dialogs.py:1338 bal/gui/qt/window.py:2321 msgid "Ko" msgstr "Error" -#: bal/gui/qt/dialogs.py:1347 +#: bal/gui/qt/dialogs.py:1349 msgid "Timeout - no answer" msgstr "Tiempo agotado - sin respuesta" -#: bal/gui/qt/dialogs.py:1389 +#: bal/gui/qt/dialogs.py:1391 #, python-brace-format msgid "checking {} - {}" msgstr "comprobando {} - {}" -#: bal/gui/qt/dialogs.py:1392 bal/gui/qt/dialogs.py:2240 -#: bal/gui/qt/window.py:1691 +#: bal/gui/qt/dialogs.py:1394 bal/gui/qt/dialogs.py:2242 bal/gui/qt/window.py:1695 msgid "Waiting" msgstr "Esperando" -#: bal/gui/qt/dialogs.py:1406 +#: bal/gui/qt/dialogs.py:1408 #, python-brace-format msgid "checked {} - {}" msgstr "comprobado {} - {}" -#: bal/gui/qt/dialogs.py:1456 bal/gui/qt/dialogs.py:2026 -#: bal/gui/qt/dialogs.py:2037 +#: bal/gui/qt/dialogs.py:1458 bal/gui/qt/dialogs.py:2028 bal/gui/qt/dialogs.py:2039 #, python-brace-format msgid "Error: {}" msgstr "Error: {}" -#: bal/gui/qt/dialogs.py:1517 +#: bal/gui/qt/dialogs.py:1519 msgid "" "Your will has expired and must be invalidated before it can be rebuilt.\n" "A transaction window will now open:\n" @@ -434,11 +422,11 @@ msgstr "" "firma la transacción y luego TRANSMÍTELA para invalidar tu antiguo testamento.\n" "Cuando se confirme la invalidación, pulsa el botón Comprobar para terminar el testamento." -#: bal/gui/qt/dialogs.py:1546 bal/gui/qt/window.py:849 +#: bal/gui/qt/dialogs.py:1549 bal/gui/qt/window.py:852 msgid "Check Alive must be after today and before the delivery date, please update it" msgstr "La fecha de verificación de vida debe ser posterior a hoy y anterior a la fecha de entrega: actualízala" -#: bal/gui/qt/dialogs.py:1569 +#: bal/gui/qt/dialogs.py:1571 msgid "" "Your old will has been invalidated and the transaction was broadcast.\n" "Electrum may need a little time to register it.\n" @@ -448,19 +436,19 @@ msgstr "" "Electrum puede tardar un poco en registrarla.\n" "Espera a que se confirme la transacción de invalidación y vuelve a pulsar el botón Comprobar para terminar de actualizar tu testamento." -#: bal/gui/qt/dialogs.py:1585 +#: bal/gui/qt/dialogs.py:1587 msgid "Invalidate your old will" msgstr "Invalida tu antiguo testamento" -#: bal/gui/qt/dialogs.py:1599 bal/gui/qt/dialogs.py:1644 +#: bal/gui/qt/dialogs.py:1601 bal/gui/qt/dialogs.py:1646 msgid "Aborted" msgstr "Abortado" -#: bal/gui/qt/dialogs.py:1618 +#: bal/gui/qt/dialogs.py:1620 msgid "Auto-sign disabled — sign manually" msgstr "Firma automática desactivada — firma manualmente" -#: bal/gui/qt/dialogs.py:1631 +#: bal/gui/qt/dialogs.py:1633 msgid "" "The delivery date was automatically moved one day earlier so the updated will can correctly replace the previous one.\n" "Please sign (and broadcast) to confirm the change." @@ -468,134 +456,131 @@ msgstr "" "La fecha de entrega se ha adelantado automáticamente un día para que el testamento actualizado pueda sustituir correctamente al anterior.\n" "Firma (y transmite) para confirmar el cambio." -#: bal/gui/qt/dialogs.py:1641 +#: bal/gui/qt/dialogs.py:1643 msgid "Sign your will" msgstr "Firma tu testamento" -#: bal/gui/qt/dialogs.py:1646 bal/gui/qt/dialogs.py:1929 +#: bal/gui/qt/dialogs.py:1648 bal/gui/qt/dialogs.py:1931 msgid "Nothing to do" msgstr "Nada que hacer" -#: bal/gui/qt/dialogs.py:1665 +#: bal/gui/qt/dialogs.py:1667 msgid "Finished" msgstr "Terminado" -#: bal/gui/qt/dialogs.py:1682 +#: bal/gui/qt/dialogs.py:1684 msgid "Calendar" msgstr "Calendario" -#: bal/gui/qt/dialogs.py:1684 bal/gui/qt/dialogs.py:1714 -#: bal/gui/qt/dialogs.py:1781 bal/gui/qt/dialogs.py:2029 -#: bal/gui/qt/dialogs.py:2976 bal/gui/qt/dialogs.py:3231 -#: bal/gui/qt/dialogs.py:4067 +#: bal/gui/qt/dialogs.py:1686 bal/gui/qt/dialogs.py:1716 bal/gui/qt/dialogs.py:1783 bal/gui/qt/dialogs.py:2031 bal/gui/qt/dialogs.py:2978 bal/gui/qt/dialogs.py:3233 bal/gui/qt/dialogs.py:4069 msgid "Close" msgstr "Cerrar" -#: bal/gui/qt/dialogs.py:1710 bal/gui/qt/dialogs.py:1777 +#: bal/gui/qt/dialogs.py:1712 bal/gui/qt/dialogs.py:1779 msgid "🔮 Wizard" msgstr "🔮 Asistente" -#: bal/gui/qt/dialogs.py:1773 bal/gui/qt/dialogs.py:2581 +#: bal/gui/qt/dialogs.py:1775 bal/gui/qt/dialogs.py:2583 msgid "Heirs" msgstr "Herederos" -#: bal/gui/qt/dialogs.py:1882 bal/gui/qt/widgets.py:1150 +#: bal/gui/qt/dialogs.py:1884 bal/gui/qt/widgets.py:1150 #, python-brace-format msgid "(reminder {idx}/{total})" msgstr "(recordatorio {idx}/{total})" -#: bal/gui/qt/dialogs.py:1946 +#: bal/gui/qt/dialogs.py:1948 msgid "All done" msgstr "Todo listo" -#: bal/gui/qt/dialogs.py:1997 +#: bal/gui/qt/dialogs.py:2000 msgid "Next steps (manual): press 'Sign' to sign your will, then 'Broadcast' to send it to the will-executors." msgstr "Siguientes pasos (manual): pulsa 'Firmar' para firmar tu testamento y luego 'Transmitir' para enviarlo a los Will-Executor." -#: bal/gui/qt/dialogs.py:2002 +#: bal/gui/qt/dialogs.py:2005 msgid "Next step (manual): press 'Sign' to sign your will." msgstr "Siguiente paso (manual): pulsa 'Firmar' para firmar tu testamento." -#: bal/gui/qt/dialogs.py:2006 +#: bal/gui/qt/dialogs.py:2009 msgid "Next step (manual): press 'Broadcast' to send your will to the will-executors." msgstr "Siguiente paso (manual): pulsa 'Transmitir' para enviar tu testamento a los Will-Executor." -#: bal/gui/qt/dialogs.py:2101 +#: bal/gui/qt/dialogs.py:2104 #, python-brace-format msgid "Miner fee rate changed (the will was built with {} sat/byte, now it is {}): a new will must be prepared." msgstr "La tasa de comisión de minería ha cambiado (el testamento se creó con {} sat/byte, ahora es {}): hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2106 +#: bal/gui/qt/dialogs.py:2109 msgid "The miner fee rate changed: a new will must be prepared." msgstr "La tasa de comisión de minería ha cambiado: hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2110 +#: bal/gui/qt/dialogs.py:2112 #, python-brace-format msgid "Will-executor \"{}\" is not covered by the current will: a new will must be prepared." msgstr "El Will-Executor \"{}\" no está cubierto por el testamento actual: hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2115 +#: bal/gui/qt/dialogs.py:2118 msgid "Backup mode is enabled but the will has no backup transaction: a new will must be prepared." msgstr "El modo de copia de seguridad está activado pero el testamento no tiene transacción de copia de seguridad: hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2124 +#: bal/gui/qt/dialogs.py:2126 #, python-brace-format msgid "Heir \"{}\" is not covered by the current will (it was added or removed, or its delivery date changed): a new will must be prepared." msgstr "El heredero \"{}\" no está cubierto por el testamento actual (se añadió o se eliminó, o cambió su fecha de entrega): hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2133 +#: bal/gui/qt/dialogs.py:2135 msgid "The heirs changed: a new will must be prepared." msgstr "Los herederos han cambiado: hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2135 +#: bal/gui/qt/dialogs.py:2137 msgid "A will-executor changed: a new will must be prepared." msgstr "Un Will-Executor ha cambiado: hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2144 +#: bal/gui/qt/dialogs.py:2147 msgid "The wallet contains funds that the current will does not cover yet: a new will must be prepared." msgstr "La cartera contiene fondos que el testamento actual aún no cubre: hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2148 +#: bal/gui/qt/dialogs.py:2151 msgid "The will contains no valid transaction: a new will must be prepared." msgstr "El testamento no contiene ninguna transacción válida: hay que preparar un nuevo testamento." -#: bal/gui/qt/dialogs.py:2154 +#: bal/gui/qt/dialogs.py:2157 msgid "The will is no longer coherent and must be rebuilt; the exact reason could not be determined." msgstr "El testamento ya no es coherente y debe recrearse; no se pudo determinar el motivo exacto." -#: bal/gui/qt/dialogs.py:2184 +#: bal/gui/qt/dialogs.py:2187 msgid "No heirs: add at least one heir before building the will." msgstr "Sin herederos: añade al menos un heredero antes de crear el testamento." -#: bal/gui/qt/dialogs.py:2187 +#: bal/gui/qt/dialogs.py:2190 msgid "The wallet has no spendable funds, so no inheritance transaction can be created." msgstr "La cartera no tiene fondos gastables, por lo que no se puede crear ninguna transacción de herencia." -#: bal/gui/qt/dialogs.py:2191 +#: bal/gui/qt/dialogs.py:2194 msgid "No usable will-executor: none of the servers in the list is both selected and valid. Open the will-executor settings, select at least one server and check that it is reachable." msgstr "No hay ningún Will-Executor utilizable: ninguno de los servidores de la lista está a la vez seleccionado y es válido. Abre la configuración de los Will-Executor, selecciona al menos un servidor y comprueba que es accesible." -#: bal/gui/qt/dialogs.py:2196 +#: bal/gui/qt/dialogs.py:2199 msgid "No delivery date left to build: every heir's date is already covered by the existing will. Choose a later delivery date, or change an heir's date." msgstr "No queda ninguna fecha de entrega para crear: la fecha de cada heredero ya está cubierta por el testamento existente. Elige una fecha de entrega posterior o cambia la fecha de un heredero." -#: bal/gui/qt/dialogs.py:2201 +#: bal/gui/qt/dialogs.py:2204 msgid "The amount to send must cover the miner fees plus this will-executor's fee, and the wallet balance is not enough: select cheaper will-executors, or add funds to the wallet." msgstr "El importe a enviar debe cubrir las comisiones de minería más la comisión de este Will-Executor, y el saldo de la cartera no es suficiente: selecciona Will-Executor más baratos o añade fondos a la cartera." -#: bal/gui/qt/dialogs.py:2206 +#: bal/gui/qt/dialogs.py:2209 msgid "A will-executor asks for more than the maximum fee you allowed: raise the maximum fee in the settings, or select a cheaper will-executor." msgstr "Un Will-Executor pide más que la comisión máxima que permitiste: sube la comisión máxima en la configuración o selecciona un Will-Executor más barato." -#: bal/gui/qt/dialogs.py:2211 +#: bal/gui/qt/dialogs.py:2214 msgid "The inheritance transactions could not be assembled from the available funds (the balance may not cover the miner fees)." msgstr "No se pudieron componer las transacciones de herencia con los fondos disponibles (el saldo puede no cubrir las comisiones de minería)." -#: bal/gui/qt/dialogs.py:2215 +#: bal/gui/qt/dialogs.py:2218 msgid "An unexpected error stopped the transactions being prepared for a will-executor, so it was skipped. Try again, or select a different will-executor." msgstr "Un error inesperado detuvo la preparación de las transacciones para un Will-Executor, así que se omitió. Inténtalo de nuevo o selecciona otro Will-Executor." -#: bal/gui/qt/dialogs.py:2226 +#: bal/gui/qt/dialogs.py:2228 msgid "" "Could not build the will, and the exact cause could not be determined. Please check that:\n" "- the wallet balance covers the miner and will-executor fees,\n" @@ -609,127 +594,126 @@ msgstr "" "- la fecha de verificación de vida es ANTERIOR a la fecha de entrega,\n" "- hay al menos un Will-Executor seleccionado y accesible." -#: bal/gui/qt/dialogs.py:2247 +#: bal/gui/qt/dialogs.py:2249 msgid "Invalidating old will" msgstr "Invalidando el antiguo testamento" -#: bal/gui/qt/dialogs.py:2253 +#: bal/gui/qt/dialogs.py:2255 msgid "Building your will" msgstr "Creando tu testamento" -#: bal/gui/qt/dialogs.py:2259 +#: bal/gui/qt/dialogs.py:2261 msgid "Signing your will" msgstr "Firmando tu testamento" -#: bal/gui/qt/dialogs.py:2265 +#: bal/gui/qt/dialogs.py:2267 msgid "Broadcasting your will to executors" msgstr "Transmitiendo tu testamento a los ejecutores" -#: bal/gui/qt/dialogs.py:2313 +#: bal/gui/qt/dialogs.py:2315 msgid "Wait" msgstr "Espera" -#: bal/gui/qt/dialogs.py:2416 +#: bal/gui/qt/dialogs.py:2418 msgid "Will Details" msgstr "Detalles del testamento" -#: bal/gui/qt/dialogs.py:2422 bal/gui/qt/dialogs.py:4042 -#: bal/gui/qt/lists.py:668 +#: bal/gui/qt/dialogs.py:2424 bal/gui/qt/dialogs.py:4044 bal/gui/qt/lists.py:668 msgid "Sign" msgstr "Firmar" -#: bal/gui/qt/dialogs.py:2426 bal/gui/qt/lists.py:675 +#: bal/gui/qt/dialogs.py:2428 bal/gui/qt/lists.py:675 msgid "Broadcast" msgstr "Transmitir" -#: bal/gui/qt/dialogs.py:2433 bal/gui/qt/lists.py:677 +#: bal/gui/qt/dialogs.py:2435 bal/gui/qt/lists.py:677 msgid "Invalidate" msgstr "Invalidar" -#: bal/gui/qt/dialogs.py:2438 bal/gui/qt/lists.py:674 +#: bal/gui/qt/dialogs.py:2440 bal/gui/qt/lists.py:674 msgid "Merge" msgstr "Combinar" -#: bal/gui/qt/dialogs.py:2446 +#: bal/gui/qt/dialogs.py:2448 #, python-brace-format msgid "Expiration date: {}" msgstr "Fecha de caducidad: {}" -#: bal/gui/qt/dialogs.py:2452 +#: bal/gui/qt/dialogs.py:2454 #, python-brace-format msgid "Valid Txs:{}" msgstr "Transacciones válidas: {}" -#: bal/gui/qt/dialogs.py:2454 +#: bal/gui/qt/dialogs.py:2456 #, python-brace-format msgid "Total Txs:{}" msgstr "Transacciones totales: {}" -#: bal/gui/qt/dialogs.py:2508 +#: bal/gui/qt/dialogs.py:2510 msgid "Hide replaced" msgstr "Ocultar sustituidas" -#: bal/gui/qt/dialogs.py:2510 +#: bal/gui/qt/dialogs.py:2512 msgid "Unhide replaced" msgstr "Mostrar sustituidas" -#: bal/gui/qt/dialogs.py:2516 +#: bal/gui/qt/dialogs.py:2518 msgid "Hide invalidated" msgstr "Ocultar invalidadas" -#: bal/gui/qt/dialogs.py:2518 +#: bal/gui/qt/dialogs.py:2520 msgid "Unhide invalidated" msgstr "Mostrar invalidadas" -#: bal/gui/qt/dialogs.py:2543 +#: bal/gui/qt/dialogs.py:2545 msgid "Will-Executor Service List" msgstr "Lista de servicios Will-Executor" -#: bal/gui/qt/dialogs.py:2590 bal/gui/qt/lists.py:314 +#: bal/gui/qt/dialogs.py:2592 bal/gui/qt/lists.py:314 msgid "New Heir" msgstr "Nuevo heredero" -#: bal/gui/qt/dialogs.py:2632 +#: bal/gui/qt/dialogs.py:2634 msgid "All" msgstr "Todo" -#: bal/gui/qt/dialogs.py:2635 +#: bal/gui/qt/dialogs.py:2637 msgid "Valid NC" msgstr "Válido NC" -#: bal/gui/qt/dialogs.py:2773 bal/gui/qt/dialogs.py:2851 +#: bal/gui/qt/dialogs.py:2775 bal/gui/qt/dialogs.py:2853 msgid "Auto" msgstr "Automático" -#: bal/gui/qt/dialogs.py:2775 +#: bal/gui/qt/dialogs.py:2777 msgid "Automatically advance through the QR codes." msgstr "Avanza automáticamente por los códigos QR." -#: bal/gui/qt/dialogs.py:2779 +#: bal/gui/qt/dialogs.py:2781 msgid "QR codes per second:" msgstr "Códigos QR por segundo:" -#: bal/gui/qt/dialogs.py:2783 +#: bal/gui/qt/dialogs.py:2785 msgid " /s" msgstr " /s" -#: bal/gui/qt/dialogs.py:2785 +#: bal/gui/qt/dialogs.py:2787 msgid "Loop" msgstr "Bucle" -#: bal/gui/qt/dialogs.py:2787 +#: bal/gui/qt/dialogs.py:2790 msgid "When the last QR code is reached, keep cycling from the first one instead of stopping." msgstr "Cuando se alcanza el último código QR, sigue repitiendo desde el primero en lugar de detenerse." -#: bal/gui/qt/dialogs.py:2797 +#: bal/gui/qt/dialogs.py:2799 msgid "QR code size:" msgstr "Tamaño del código QR:" -#: bal/gui/qt/dialogs.py:2845 +#: bal/gui/qt/dialogs.py:2847 msgid "Stop" msgstr "Detener" -#: bal/gui/qt/dialogs.py:2866 +#: bal/gui/qt/dialogs.py:2868 #, python-brace-format msgid "" "Scan the QR codes below with the will-opening device.\n" @@ -738,101 +722,98 @@ msgstr "" "Escanea los códigos QR de abajo con el dispositivo que abre el testamento.\n" "{} códigos contienen la transferencia completa (sirve cualquier orden y se ignoran los duplicados)." -#: bal/gui/qt/dialogs.py:2888 +#: bal/gui/qt/dialogs.py:2890 #, python-brace-format msgid "Frame {} of {}" msgstr "Código {} de {}" -#: bal/gui/qt/dialogs.py:2911 +#: bal/gui/qt/dialogs.py:2913 msgid "Export will" msgstr "Exportar testamento" -#: bal/gui/qt/dialogs.py:2922 +#: bal/gui/qt/dialogs.py:2924 msgid "No will transaction to export." msgstr "No hay ninguna transacción del testamento que exportar." -#: bal/gui/qt/dialogs.py:2929 +#: bal/gui/qt/dialogs.py:2931 msgid "Export:" msgstr "Exportar:" -#: bal/gui/qt/dialogs.py:2936 +#: bal/gui/qt/dialogs.py:2938 msgid "Whole will" msgstr "Testamento completo" -#: bal/gui/qt/dialogs.py:2939 +#: bal/gui/qt/dialogs.py:2942 msgid "Export the full will items (all details/statuses) as JSON, or only the serialized transactions." msgstr "Exporta los elementos completos del testamento (todos los detalles y estados) en JSON, o solo las transacciones serializadas." -#: bal/gui/qt/dialogs.py:2950 +#: bal/gui/qt/dialogs.py:2952 msgid "Send as:" msgstr "Enviar como:" -#: bal/gui/qt/dialogs.py:2952 bal/gui/qt/dialogs.py:3208 +#: bal/gui/qt/dialogs.py:2954 bal/gui/qt/dialogs.py:3210 msgid "File" msgstr "Archivo" -#: bal/gui/qt/dialogs.py:2953 bal/gui/qt/dialogs.py:3209 +#: bal/gui/qt/dialogs.py:2955 bal/gui/qt/dialogs.py:3211 msgid "QR Code" msgstr "Código QR" -#: bal/gui/qt/dialogs.py:2954 bal/gui/qt/dialogs.py:3210 +#: bal/gui/qt/dialogs.py:2956 bal/gui/qt/dialogs.py:3212 msgid "Audio" msgstr "Audio" -#: bal/gui/qt/dialogs.py:3005 +#: bal/gui/qt/dialogs.py:3007 msgid "Export the will as a JSON file. Uncheck \"Whole will\" above to export only the serialized transactions (comma-separated)." msgstr "Exporta el testamento como archivo JSON. Desmarca \"Testamento completo\" arriba para exportar solo las transacciones serializadas (separadas por comas)." -#: bal/gui/qt/dialogs.py:3014 +#: bal/gui/qt/dialogs.py:3016 msgid "Export…" msgstr "Exportar…" -#: bal/gui/qt/dialogs.py:3032 bal/gui/qt/dialogs.py:3271 -#: bal/gui/qt/window.py:1787 bal/gui/qt/window.py:1800 +#: bal/gui/qt/dialogs.py:3034 bal/gui/qt/dialogs.py:3273 bal/gui/qt/window.py:1791 bal/gui/qt/window.py:1804 msgid "Audio MODEM plugin is not available." msgstr "El plugin del MÓDEM de audio no está disponible." -#: bal/gui/qt/dialogs.py:3036 bal/gui/qt/dialogs.py:3275 +#: bal/gui/qt/dialogs.py:3038 bal/gui/qt/dialogs.py:3277 msgid "Speed (KB/sec):" msgstr "Velocidad (KB/s):" -#: bal/gui/qt/dialogs.py:3047 +#: bal/gui/qt/dialogs.py:3049 msgid "Send" msgstr "Enviar" -#: bal/gui/qt/dialogs.py:3100 bal/gui/qt/dialogs.py:3134 -#: bal/gui/qt/dialogs.py:3159 +#: bal/gui/qt/dialogs.py:3102 bal/gui/qt/dialogs.py:3136 bal/gui/qt/dialogs.py:3161 msgid "No will transaction matches the selected filter." msgstr "Ninguna transacción del testamento coincide con el filtro seleccionado." -#: bal/gui/qt/dialogs.py:3123 +#: bal/gui/qt/dialogs.py:3125 msgid "item(s)" msgstr "elemento(s)" -#: bal/gui/qt/dialogs.py:3123 +#: bal/gui/qt/dialogs.py:3125 msgid "transaction(s)" msgstr "transacción(es)" -#: bal/gui/qt/dialogs.py:3125 +#: bal/gui/qt/dialogs.py:3127 #, python-brace-format msgid "{} {} will be exported." msgstr "Se exportará: {} {}." -#: bal/gui/qt/dialogs.py:3128 +#: bal/gui/qt/dialogs.py:3130 #, python-brace-format msgid "{} {} will be sent." msgstr "Se enviará: {} {}." -#: bal/gui/qt/dialogs.py:3152 bal/gui/qt/dialogs.py:3155 -#: bal/gui/qt/dialogs.py:3311 bal/gui/qt/dialogs.py:3314 +#: bal/gui/qt/dialogs.py:3154 bal/gui/qt/dialogs.py:3157 bal/gui/qt/dialogs.py:3313 bal/gui/qt/dialogs.py:3316 msgid "Invalid audio speed." msgstr "Velocidad de audio no válida." -#: bal/gui/qt/dialogs.py:3191 +#: bal/gui/qt/dialogs.py:3193 msgid "Import will" msgstr "Importar testamento" -#: bal/gui/qt/dialogs.py:3197 +#: bal/gui/qt/dialogs.py:3199 msgid "" "Choose how the will was exported: from a file, QR codes or audio.\n" "The import never touches the live will." @@ -840,59 +821,59 @@ msgstr "" "Elige cómo se exportó el testamento: desde un archivo, códigos QR o audio.\n" "La importación nunca afecta al testamento activo." -#: bal/gui/qt/dialogs.py:3206 +#: bal/gui/qt/dialogs.py:3208 msgid "Import from:" msgstr "Importar desde:" -#: bal/gui/qt/dialogs.py:3246 +#: bal/gui/qt/dialogs.py:3249 msgid "Import the JSON will file written by the Export ▶ File option. The will opens in a read-only preview window." msgstr "Importa el archivo JSON del testamento creado con la opción Exportar ▶ Archivo. El testamento se abre en una ventana de vista previa de solo lectura." -#: bal/gui/qt/dialogs.py:3253 +#: bal/gui/qt/dialogs.py:3255 msgid "Choose will file…" msgstr "Elige el archivo del testamento…" -#: bal/gui/qt/dialogs.py:3284 +#: bal/gui/qt/dialogs.py:3286 msgid "Waiting for the audio transfer…" msgstr "Esperando la transferencia de audio…" -#: bal/gui/qt/dialogs.py:3287 +#: bal/gui/qt/dialogs.py:3289 msgid "Receive by audio…" msgstr "Recibir por audio…" -#: bal/gui/qt/dialogs.py:3328 +#: bal/gui/qt/dialogs.py:3330 msgid "Receiving…" msgstr "Recibiendo…" -#: bal/gui/qt/dialogs.py:3348 +#: bal/gui/qt/dialogs.py:3350 msgid "No transaction data received." msgstr "No se recibió ningún dato de transacción." -#: bal/gui/qt/dialogs.py:3366 +#: bal/gui/qt/dialogs.py:3368 #, python-brace-format msgid "Waiting for audio ({:.1f} kbps)…" msgstr "Esperando audio ({:.1f} kbps)…" -#: bal/gui/qt/dialogs.py:3439 +#: bal/gui/qt/dialogs.py:3441 #, python-brace-format msgid "Could not parse a transferred will item: {}" msgstr "No se pudo interpretar un elemento del testamento transferido: {}" -#: bal/gui/qt/dialogs.py:3459 +#: bal/gui/qt/dialogs.py:3461 #, python-brace-format msgid "Could not parse a transferred transaction: {}" msgstr "No se pudo interpretar una transacción transferida: {}" -#: bal/gui/qt/dialogs.py:3470 +#: bal/gui/qt/dialogs.py:3473 msgid "The imported will contains no valid transaction in this wallet." msgstr "El testamento importado no contiene ninguna transacción válida en esta cartera." -#: bal/gui/qt/dialogs.py:3481 +#: bal/gui/qt/dialogs.py:3484 #, python-brace-format msgid "{} imported transaction(s) are not valid in this wallet and were skipped." msgstr "{} transacción(es) importada(s) no son válidas en esta cartera y se omitieron." -#: bal/gui/qt/dialogs.py:3581 +#: bal/gui/qt/dialogs.py:3583 msgid "" "Show the will QR codes to the camera, one after another.\n" "After the first code, every new code is captured automatically.\n" @@ -902,114 +883,114 @@ msgstr "" "Después del primer código, cada código nuevo se captura automáticamente.\n" "El primer código indica el número total de códigos; se ignoran los duplicados." -#: bal/gui/qt/dialogs.py:3591 bal/gui/qt/dialogs.py:3769 +#: bal/gui/qt/dialogs.py:3593 bal/gui/qt/dialogs.py:3771 msgid "Waiting for the first frame…" msgstr "Esperando el primer código…" -#: bal/gui/qt/dialogs.py:3608 +#: bal/gui/qt/dialogs.py:3610 msgid "…or paste/type the frame text here" msgstr "…o pega/escribe aquí el texto del código" -#: bal/gui/qt/dialogs.py:3612 +#: bal/gui/qt/dialogs.py:3614 msgid "Add frame" msgstr "Añadir código" -#: bal/gui/qt/dialogs.py:3618 bal/gui/qt/dialogs.py:3861 +#: bal/gui/qt/dialogs.py:3620 bal/gui/qt/dialogs.py:3863 msgid "Scan with camera…" msgstr "Escanear con la cámara…" -#: bal/gui/qt/dialogs.py:3620 +#: bal/gui/qt/dialogs.py:3623 msgid "Start the live camera. While it runs, every new QR code shown to the camera is captured automatically." msgstr "Inicia la cámara en vivo. Mientras esté en marcha, cada código QR nuevo que se muestre a la cámara se capturará automáticamente." -#: bal/gui/qt/dialogs.py:3627 +#: bal/gui/qt/dialogs.py:3629 msgid "Reset" msgstr "Reiniciar" -#: bal/gui/qt/dialogs.py:3634 +#: bal/gui/qt/dialogs.py:3636 msgid "Review and Sign…" msgstr "Revisar y firmar…" -#: bal/gui/qt/dialogs.py:3653 +#: bal/gui/qt/dialogs.py:3656 #, python-brace-format msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; scan the first code again." msgstr "El código escaneado pertenece a otra transferencia ({} códigos, {}). Se reinició la importación; escanea de nuevo el primer código." -#: bal/gui/qt/dialogs.py:3744 +#: bal/gui/qt/dialogs.py:3746 #, python-brace-format msgid "All {} frames stored." msgstr "Se guardaron los {} códigos." -#: bal/gui/qt/dialogs.py:3750 +#: bal/gui/qt/dialogs.py:3752 #, python-brace-format msgid "Stored {} of {} frames." msgstr "Guardados {} de {} códigos." -#: bal/gui/qt/dialogs.py:3815 +#: bal/gui/qt/dialogs.py:3817 msgid "Cannot start QR scanner, no usable camera found." msgstr "No se puede inciar scanner QR, no se encontró la camara." -#: bal/gui/qt/dialogs.py:3820 +#: bal/gui/qt/dialogs.py:3822 msgid "Stop scanning" msgstr "Detener escaneo" -#: bal/gui/qt/dialogs.py:3867 +#: bal/gui/qt/dialogs.py:3869 #, python-brace-format msgid "Camera error: {}" msgstr "Error de cámara: {}" -#: bal/gui/qt/dialogs.py:3913 +#: bal/gui/qt/dialogs.py:3916 #, python-brace-format msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; show the first code again." msgstr "El código escaneado pertenece a otra transferencia ({} códigos, {}). Se reinició la importación; muestra de nuevo el primer código." -#: bal/gui/qt/dialogs.py:3957 +#: bal/gui/qt/dialogs.py:3959 msgid "The transferred will contains no transactions." msgstr "El testamento transferido no contiene transacciones." -#: bal/gui/qt/dialogs.py:3986 +#: bal/gui/qt/dialogs.py:3988 msgid "Review and sign imported will" msgstr "Revisar y firmar el testamento importado" -#: bal/gui/qt/dialogs.py:3998 +#: bal/gui/qt/dialogs.py:4001 msgid "Enter your wallet password to sign the imported transactions." msgstr "Introduce la contraseña de tu cartera para firmar las transacciones importadas." -#: bal/gui/qt/dialogs.py:4045 +#: bal/gui/qt/dialogs.py:4047 msgid "Skip" msgstr "Omitir" -#: bal/gui/qt/dialogs.py:4061 +#: bal/gui/qt/dialogs.py:4063 msgid "Save signed file…" msgstr "Guardar archivo firmado…" -#: bal/gui/qt/dialogs.py:4064 +#: bal/gui/qt/dialogs.py:4066 msgid "Show signed QR…" msgstr "Mostrar QR firmado…" -#: bal/gui/qt/dialogs.py:4088 +#: bal/gui/qt/dialogs.py:4090 #, python-brace-format msgid "Transaction {} of {}" msgstr "Transacción {} de {}" -#: bal/gui/qt/dialogs.py:4090 +#: bal/gui/qt/dialogs.py:4092 #, python-brace-format msgid "TXID: {}" msgstr "TXID: {}" -#: bal/gui/qt/dialogs.py:4093 +#: bal/gui/qt/dialogs.py:4095 msgid "unknown" msgstr "desconocido" -#: bal/gui/qt/dialogs.py:4099 +#: bal/gui/qt/dialogs.py:4101 msgid "(no outputs)" msgstr "(sin salidas)" -#: bal/gui/qt/dialogs.py:4113 +#: bal/gui/qt/dialogs.py:4115 msgid "unknown (partial transaction)" msgstr "desconocida (transacción parcial)" -#: bal/gui/qt/dialogs.py:4115 +#: bal/gui/qt/dialogs.py:4117 #, python-brace-format msgid "" "Total outputs: {}\n" @@ -1018,7 +999,7 @@ msgstr "" "Salidas totales: {}\n" "Comisión: {}" -#: bal/gui/qt/dialogs.py:4126 +#: bal/gui/qt/dialogs.py:4128 #, python-brace-format msgid "" "Signed {} of {} transactions.\n" @@ -1029,16 +1010,16 @@ msgstr "" "\n" "Guarda un archivo firmado para llevarlo al dispositivo de transmisión o muestra las transacciones firmadas como códigos QR." -#: bal/gui/qt/dialogs.py:4145 +#: bal/gui/qt/dialogs.py:4147 #, python-brace-format msgid "Could not sign the transaction: {}" msgstr "No se pudo firmar la transacción: {}" -#: bal/gui/qt/dialogs.py:4164 +#: bal/gui/qt/dialogs.py:4166 msgid "Signed will saved." msgstr "Testamento firmado guardado." -#: bal/gui/qt/dialogs.py:4175 +#: bal/gui/qt/dialogs.py:4177 msgid "No signed transaction to show." msgstr "No hay ninguna transacción firmada que mostrar." @@ -1046,8 +1027,7 @@ msgstr "No hay ninguna transacción firmada que mostrar." msgid "Name" msgstr "Nombre" -#: bal/gui/qt/lists.py:135 bal/gui/qt/lists.py:1432 bal/gui/qt/widgets.py:1420 -#: bal/gui/qt/window.py:374 +#: bal/gui/qt/lists.py:135 bal/gui/qt/lists.py:1432 bal/gui/qt/widgets.py:1420 bal/gui/qt/window.py:374 msgid "Address" msgstr "Dirección" @@ -1255,8 +1235,7 @@ msgstr "Deseleccionar solo los no válidos" msgid "Edit: {}" msgstr "Editar: {}" -#: bal/gui/qt/lists.py:1290 bal/gui/qt/lists.py:1302 bal/gui/qt/lists.py:1316 -#: bal/gui/qt/lists.py:1476 +#: bal/gui/qt/lists.py:1290 bal/gui/qt/lists.py:1302 bal/gui/qt/lists.py:1316 bal/gui/qt/lists.py:1476 msgid "New Will Executor" msgstr "Nuevo Will-Executor" @@ -1310,67 +1289,67 @@ msgstr "Ningún Will-Executor coincide con el filtro seleccionado" msgid "No will-executor is selected" msgstr "No hay ningún Will-Executor seleccionado" -#: bal/gui/qt/plugin.py:139 +#: bal/gui/qt/plugin.py:141 msgid "Electrum Plugins" msgstr "Complementos de Electrum" -#: bal/gui/qt/plugin.py:306 +#: bal/gui/qt/plugin.py:308 msgid "Bitcoin After Life" msgstr "Bitcoin After Life" -#: bal/gui/qt/plugin.py:425 bal/gui/qt/plugin.py:450 +#: bal/gui/qt/plugin.py:427 bal/gui/qt/plugin.py:452 msgid "Settings" msgstr "Ajustes" -#: bal/gui/qt/plugin.py:551 +#: bal/gui/qt/plugin.py:553 msgid "BASIC" msgstr "BÁSICO" -#: bal/gui/qt/plugin.py:551 +#: bal/gui/qt/plugin.py:553 msgid "ADVANCED" msgstr "AVANZADO" -#: bal/gui/qt/plugin.py:571 +#: bal/gui/qt/plugin.py:573 msgid "Enable ADVANCED mode" msgstr "Activar el modo AVANZADO" -#: bal/gui/qt/plugin.py:572 +#: bal/gui/qt/plugin.py:574 msgid "Type 'at My Risk' to enable ADVANCED mode" msgstr "Escribe 'at My Risk' para activar el modo AVANZADO" -#: bal/gui/qt/plugin.py:641 +#: bal/gui/qt/plugin.py:645 bal/gui/qt/plugin.py:983 msgid "Reset to default" msgstr "Restablecer a los valores predeterminados" -#: bal/gui/qt/plugin.py:659 +#: bal/gui/qt/plugin.py:663 msgid "Rebroadcast transactions" msgstr "Retransmitir transacciones" -#: bal/gui/qt/plugin.py:670 +#: bal/gui/qt/plugin.py:674 msgid "Warning: change these settings only if you know what you are doing." msgstr "Aviso: cambia estos ajustes solo si sabes lo que haces." -#: bal/gui/qt/plugin.py:685 +#: bal/gui/qt/plugin.py:689 msgid "Hide Replaced" msgstr "Ocultar sustituidas" -#: bal/gui/qt/plugin.py:688 +#: bal/gui/qt/plugin.py:692 msgid "Hide replaced transactions from will detail and list" msgstr "Oculta las transacciones sustituidas del detalle y la lista del testamento" -#: bal/gui/qt/plugin.py:693 +#: bal/gui/qt/plugin.py:697 msgid "Hide Invalidated" msgstr "Ocultar invalidadas" -#: bal/gui/qt/plugin.py:696 +#: bal/gui/qt/plugin.py:700 msgid "Hide invalidated transactions from will detail and list" msgstr "Oculta las transacciones invalidadas del detalle y la lista del testamento" -#: bal/gui/qt/plugin.py:699 +#: bal/gui/qt/plugin.py:703 msgid "Auto-sign on Check" msgstr "Firma automática al comprobar" -#: bal/gui/qt/plugin.py:700 +#: bal/gui/qt/plugin.py:702 msgid "" "When checking, automatically sign and broadcast the will transactions to their will-executors.\n" "The wallet password is requested only if the wallet is encrypted." @@ -1378,11 +1357,11 @@ msgstr "" "Al comprobar, firma y transmite automáticamente las transacciones del testamento a sus Will-Executor.\n" "La contraseña de la cartera solo se solicita si la cartera está cifrada." -#: bal/gui/qt/plugin.py:715 +#: bal/gui/qt/plugin.py:719 msgid "Panel editable Date and Fee" msgstr "Panel editable de fecha y comisión" -#: bal/gui/qt/plugin.py:718 +#: bal/gui/qt/plugin.py:720 msgid "" "When enabled, the delivery-time and check-alive date fields can be edited everywhere (toolbar / Heirs tab), not only in the will-building wizard.\n" "When disabled, those dates are display-only outside the wizard." @@ -1390,11 +1369,11 @@ msgstr "" "Si está activado, los campos de fecha de entrega y de verificación de vida se pueden editar en cualquier parte (barra de herramientas / pestaña Herederos), no solo en el asistente de creación del testamento.\n" "Si está desactivado, esas fechas son de solo lectura fuera del asistente." -#: bal/gui/qt/plugin.py:730 +#: bal/gui/qt/plugin.py:734 msgid "Max Will-Executor Fee (satoshi)" msgstr "Comisión máxima del Will-Executor (satoshi)" -#: bal/gui/qt/plugin.py:733 +#: bal/gui/qt/plugin.py:735 msgid "" "Maximum fee (in satoshi) allowed to be paid to a single will-executor. If a will-executor charges more than this, the will will not be built.\n" "Default: 500,000 satoshi (0.005 BTC)." @@ -1402,11 +1381,11 @@ msgstr "" "Comisión máxima (en satoshis) que se permite pagar a un único Will-Executor. Si un Will-Executor cobra más, el testamento no se creará.\n" "Predeterminado: 500 000 satoshis (0,005 BTC)." -#: bal/gui/qt/plugin.py:745 +#: bal/gui/qt/plugin.py:749 msgid "User Type" msgstr "Tipo de usuario" -#: bal/gui/qt/plugin.py:748 +#: bal/gui/qt/plugin.py:750 msgid "" "Choose how much detail the plugin shows.\n" "\n" @@ -1420,19 +1399,19 @@ msgstr "" "\n" "AVANZADO: muestra todos los controles, incluido el campo 'Verificación de vida' y el selector Raw/Fecha. Permite configurar la URL del servidor welist y mantiene todos los campos de fecha/comisión siempre editables." -#: bal/gui/qt/plugin.py:762 +#: bal/gui/qt/plugin.py:766 msgid "Number of reminders" msgstr "Número de recordatorios" -#: bal/gui/qt/plugin.py:763 +#: bal/gui/qt/plugin.py:768 msgid "How many reminder alarms the exported calendar (.ics) event contains. Range: 1 to 5 (default 3). Only used in ADVANCED mode." msgstr "Cuántas alarmas de recordatorio contiene el evento de calendario (.ics) exportado. Rango: de 1 a 5 (predeterminado 3). Solo se usa en modo AVANZADO." -#: bal/gui/qt/plugin.py:773 +#: bal/gui/qt/plugin.py:777 msgid "Event summary" msgstr "Resumen del evento" -#: bal/gui/qt/plugin.py:774 +#: bal/gui/qt/plugin.py:776 msgid "" "Default message to be used in event summary\n" "Variables:\n" @@ -1446,11 +1425,11 @@ msgstr "" " $heirs_complete: lista de herederos nombre,dirección,importe\n" "Solo se usa en modo AVANZADO." -#: bal/gui/qt/plugin.py:787 +#: bal/gui/qt/plugin.py:791 msgid "Event description" msgstr "Descripción del evento" -#: bal/gui/qt/plugin.py:788 +#: bal/gui/qt/plugin.py:790 msgid "" "Default message to be used in event description\n" "Variables:\n" @@ -1464,19 +1443,19 @@ msgstr "" " $heirs_complete: lista de herederos nombre,dirección,importe\n" "Solo se usa en modo AVANZADO." -#: bal/gui/qt/plugin.py:802 +#: bal/gui/qt/plugin.py:806 msgid "Welist Server URL" msgstr "URL del servidor welist" -#: bal/gui/qt/plugin.py:803 +#: bal/gui/qt/plugin.py:808 msgid "URL of the server that provides the will-executor list. Only available in ADVANCED mode." msgstr "URL del servidor que proporciona la lista de Will-Executor. Solo disponible en modo AVANZADO." -#: bal/gui/qt/plugin.py:813 +#: bal/gui/qt/plugin.py:817 msgid "Calendar app command" msgstr "Comando de la aplicación de calendario" -#: bal/gui/qt/plugin.py:814 +#: bal/gui/qt/plugin.py:816 msgid "" "Command used to open .ics calendar files.\n" "Leave empty to use the system default (xdg-open/open/start).\n" @@ -1486,11 +1465,11 @@ msgstr "" "Déjalo vacío para usar el predeterminado del sistema (xdg-open/open/start).\n" "Solo se usa en modo AVANZADO." -#: bal/gui/qt/plugin.py:828 +#: bal/gui/qt/plugin.py:832 msgid "Save inheritance transactions in history" msgstr "Guardar las transacciones de herencia en el historial" -#: bal/gui/qt/plugin.py:829 +#: bal/gui/qt/plugin.py:831 #, python-brace-format msgid "" "After each check, save the valid will transactions into the wallet's local history (the History tab), each with a label.\n" @@ -1503,11 +1482,11 @@ msgstr "" " {willexecutor}: se sustituye por la URL del Will-Executor del elemento\n" "Solo se usa en modo AVANZADO." -#: bal/gui/qt/plugin.py:842 +#: bal/gui/qt/plugin.py:846 msgid "History label" msgstr "Etiqueta del historial" -#: bal/gui/qt/plugin.py:843 +#: bal/gui/qt/plugin.py:845 #, python-brace-format msgid "" "Label applied to the will transactions saved into the wallet's local history.\n" @@ -1520,15 +1499,15 @@ msgstr "" " {willexecutor}: se sustituye por la URL del Will-Executor del elemento\n" "Solo se usa en modo AVANZADO." -#: bal/gui/qt/plugin.py:866 +#: bal/gui/qt/plugin.py:870 msgid "Broadcast all transactions to willexecutors including those already pushed" msgstr "Transmitir todas las transacciones a los Will-Executor, incluidas las ya enviadas" -#: bal/gui/qt/plugin.py:875 +#: bal/gui/qt/plugin.py:879 msgid "Rebuild will on wallet close" msgstr "Recrear el testamento al cerrar la cartera" -#: bal/gui/qt/plugin.py:876 +#: bal/gui/qt/plugin.py:878 msgid "" "Run the 'Build your will' wizard every time the wallet is closed or Electrum is quit, so the will is rebuilt and re-validated.\n" "When disabled, the will is only rebuilt when you press Check or Prepare. The last built state is still saved to the wallet." @@ -1536,11 +1515,11 @@ msgstr "" "Ejecuta el asistente 'Crea tu testamento' cada vez que se cierra la cartera o se sale de Electrum, para recrear y volver a validar el testamento.\n" "Si está desactivado, el testamento solo se recrea al pulsar Comprobar o Preparar. El último estado creado se guarda igualmente en la cartera." -#: bal/gui/qt/plugin.py:893 +#: bal/gui/qt/plugin.py:897 msgid "Rebuild automatically on new transactions" msgstr "Recrear automáticamente con nuevas transacciones" -#: bal/gui/qt/plugin.py:894 +#: bal/gui/qt/plugin.py:896 msgid "" "When a new transaction arrives for the wallet, automatically rebuild the will the same way the wizard does at wallet close: the delivery date is anticipated by one day so the new will replaces the previous one, and the rebuilt transactions are signed and sent to their will-executors.\n" "An on-chain invalidation transaction is only built when the anticipated delivery date would fall before the Check Alive threshold, or when the threshold is already in the past.\n" @@ -1550,23 +1529,39 @@ msgstr "" "Solo se crea una transacción de invalidación en cadena cuando la fecha de entrega adelantada quedaría antes del umbral de verificación de vida, o cuando el umbral ya está en el pasado.\n" "Si está desactivado (predeterminado), el testamento solo se recrea al comprobar / preparar / cerrar la cartera." -#: bal/gui/qt/plugin.py:976 +#: bal/gui/qt/plugin.py:970 +#, fuzzy +msgid "Invalidation fee policy" +msgstr "Invalidando el antiguo testamento" + +#: bal/gui/qt/plugin.py:966 +msgid "" +"Fee rate the on-chain invalidation transaction pays.\n" +"The invalidation is sent TODAY (it spends the will's coins so the old inheritance transactions become unusable), so it should pay the current network fee - not the will's mining fee rate.\n" +"\n" +"Feerate: a fixed rate (sat/vByte).\n" +"ETA: fee based on the estimated confirmation time (blocks).\n" +"Mempool: fee targeting a depth in the memory pool.\n" +"Only used in ADVANCED mode." +msgstr "" + +#: bal/gui/qt/plugin.py:1054 msgid "Reset to Default Setting" msgstr "Restablecer a los valores predeterminados" -#: bal/gui/qt/plugin.py:977 +#: bal/gui/qt/plugin.py:1055 msgid "Reset these settings to their default values" msgstr "Restablece estos ajustes a sus valores predeterminados" -#: bal/gui/qt/plugin.py:984 +#: bal/gui/qt/plugin.py:1062 msgid "Open the Bitcoin After Life support website" msgstr "Abrir el sitio web de soporte de Bitcoin After Life" -#: bal/gui/qt/plugin.py:996 +#: bal/gui/qt/plugin.py:1074 msgid "Support:" msgstr "Soporte:" -#: bal/gui/qt/plugin.py:1053 +#: bal/gui/qt/plugin.py:1131 #, python-brace-format msgid "BAL - {}" msgstr "BAL - {}" @@ -1633,7 +1628,7 @@ msgstr "Raw" msgid "Date" msgstr "Fecha" -#: bal/gui/qt/widgets.py:645 +#: bal/gui/qt/widgets.py:646 msgid "CHECK ALIVE

In DATA mode:
set the date for the “check alive” parameter.
When you open the wallet, if the “check alive” date has passed, the plugin will ask if you want to postpone the inheritance, since it assumes you still have control of the wallet and that you are still alive.

In RAW mode:
When less than this time is missing, ask to invalidate.
If you fail to invalidate during this time, your transactions will be delivered to your heirs.

if you choose Raw, you can insert various options based on suffix:
- d: number of days after current day(ex: 1d means tomorrow)
- y: number of years after current day(ex: 1y means one year from today)
The sign selects the reference date:
- a NEGATIVE value (ex: -30d) counts backwards from the delivery date (30 days before the delivery);
- a POSITIVE value (ex: 30d) counts forward from the inheritance creation date (30 days after the will was created).

Note: the date/time is expressed in your computer's Local time (not UTC time).
" msgstr "VERIFICACIÓN DE VIDA

En modo DATA:
establece la fecha del parámetro “verificación de vida”.
Cuando abras la cartera, si la fecha de “verificación de vida” ya ha pasado, el plugin te preguntará si quieres posponer la herencia, porque asume que sigues teniendo el control de la cartera y que sigues vivo.

En modo RAW:
cuando falte menos de este tiempo, pide invalidar.
Si no invalidas durante este tiempo, tus transacciones se entregarán a tus herederos.

si eliges Raw, puedes introducir varias opciones según el sufijo:
- d: número de días a partir de hoy (ej.: 1d significa mañana)
- y: número de años a partir de hoy (ej.: 1y significa dentro de un año)
El signo selecciona la fecha de referencia:
- un valor NEGATIVO (ej.: -30d) cuenta hacia atrás desde la fecha de entrega (30 días antes de la entrega);
- un valor POSITIVO (ej.: 30d) cuenta hacia delante desde la fecha de creación de la herencia (30 días después de crear el testamento).

Nota: la fecha y la hora se expresan en la hora local de tu ordenador (no en hora UTC).
" @@ -1641,7 +1636,7 @@ msgstr "VERIFICACIÓN DE VIDA

En modo DATA:
establece la fecha msgid "Check Alive" msgstr "Verificación de vida" -#: bal/gui/qt/widgets.py:686 +#: bal/gui/qt/widgets.py:687 msgid "DELIVERY TIME

Set Locktime for transactions.
Any time is needed transaction will be anticipated by 1day

(ONLY IN ADVANCED MODE)
if you choose Raw, you can insert various options based on suffix:
- d: number of days after current day(ex: 1d means tomorrow)
- y: number of years after currrent day(ex: 1y means one year from today)

Note: the date/time is expressed in your computer's Local time (not UTC time).
" msgstr "FECHA DE ENTREGA

Establece el locktime de las transacciones.
Cuando haga falta, la transacción se adelanta 1 día.

(SOLO EN MODO AVANZADO)
si eliges Raw, puedes introducir varias opciones según el sufijo:
- d: número de días a partir de hoy (ej.: 1d significa mañana)
- y: número de años a partir de hoy (ej.: 1y significa dentro de un año)

Nota: la fecha y la hora se expresan en la hora local de tu ordenador (no en hora UTC).
" @@ -1738,28 +1733,28 @@ msgstr "locktime" msgid "heirs" msgstr "herederos" -#: bal/gui/qt/window.py:720 +#: bal/gui/qt/window.py:722 msgid "Heirs are not defined" msgstr "Los herederos no están definidos" -#: bal/gui/qt/window.py:838 +#: bal/gui/qt/window.py:841 #, python-brace-format msgid "In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.{}" msgstr "En el proceso de herencia, la cartera siempre se vaciará por completo. Tu configuración requiere un ajuste de los importes.{}" -#: bal/gui/qt/window.py:856 +#: bal/gui/qt/window.py:859 msgid "CheckAlive is in the past please update it to a date in the future but less than locktime" msgstr "La fecha de verificación de vida está en el pasado: actualízala a una fecha futura pero anterior al locktime" -#: bal/gui/qt/window.py:862 +#: bal/gui/qt/window.py:864 msgid "locktime is lower than threshold" msgstr "el locktime es anterior al umbral de verificación de vida" -#: bal/gui/qt/window.py:876 +#: bal/gui/qt/window.py:878 msgid " no backup transaction or willexecutor selected" msgstr " sin transacción de copia de seguridad ni Will-Executor seleccionado" -#: bal/gui/qt/window.py:915 +#: bal/gui/qt/window.py:917 msgid "" "This inheritance was already signed/sent to will-executors and you are postponing it.\n" "\n" @@ -1773,23 +1768,23 @@ msgstr "" "\n" "Firma y transmite ahora la transacción de invalidación, y luego pulsa 'Preparar' de nuevo para crear la nueva herencia (pospuesta)." -#: bal/gui/qt/window.py:937 +#: bal/gui/qt/window.py:939 msgid "Heirs changed:" msgstr "Herederos cambiados:" -#: bal/gui/qt/window.py:939 +#: bal/gui/qt/window.py:941 msgid "Will-Executor not present:" msgstr "Will-Executor no presente:" -#: bal/gui/qt/window.py:941 +#: bal/gui/qt/window.py:943 msgid "Will-Executor changed" msgstr "Will-Executor cambiado" -#: bal/gui/qt/window.py:943 +#: bal/gui/qt/window.py:945 msgid "Txfees are changed" msgstr "Las comisiones de transacción han cambiado" -#: bal/gui/qt/window.py:950 +#: bal/gui/qt/window.py:952 msgid "" "Found CHANGES to the DATE or the HEIRS,\n" "a NEW WILL must be prepared." @@ -1797,11 +1792,11 @@ msgstr "" "Se han encontrado CAMBIOS en la FECHA o en los HEREDEROS,\n" "hay que preparar un NUEVO TESTAMENTO." -#: bal/gui/qt/window.py:957 +#: bal/gui/qt/window.py:959 msgid "will have to be built" msgstr "habrá que crear el testamento" -#: bal/gui/qt/window.py:977 +#: bal/gui/qt/window.py:979 #, python-brace-format msgid "" "Error:{}\n" @@ -1810,11 +1805,11 @@ msgstr "" "Error:{}\n" " {}" -#: bal/gui/qt/window.py:979 +#: bal/gui/qt/window.py:981 msgid "Please, check your heirs, locktime and threshold!" msgstr "¡Comprueba tus herederos, el locktime y el umbral de verificación de vida!" -#: bal/gui/qt/window.py:993 +#: bal/gui/qt/window.py:995 msgid "" "Your inheritance has been rebuilt and now needs to be signed again.\n" "\n" @@ -1826,7 +1821,7 @@ msgstr "" "Siguiente paso (manual):\n" " 1. Pulsa 'Firmar' para firmar la nueva transacción." -#: bal/gui/qt/window.py:1000 +#: bal/gui/qt/window.py:1002 msgid "" "Your inheritance has been rebuilt and now needs to be signed and re-sent to the will-executors.\n" "\n" @@ -1840,93 +1835,92 @@ msgstr "" " 1. Pulsa 'Firmar' para firmar la nueva transacción.\n" " 2. Pulsa 'Transmitir' para enviarla a los Will-Executor." -#: bal/gui/qt/window.py:1038 +#: bal/gui/qt/window.py:1040 msgid "Electrum was unable to deserialize the transaction:" msgstr "Electrum no ha podido analizar la transacción:" -#: bal/gui/qt/window.py:1052 +#: bal/gui/qt/window.py:1054 msgid "no tx" msgstr "sin transacciones" -#: bal/gui/qt/window.py:1066 +#: bal/gui/qt/window.py:1069 msgid "Please sign and broadcast this transaction to invalidate current will" msgstr "Firma y transmite esta transacción para invalidar el testamento actual" -#: bal/gui/qt/window.py:1074 +#: bal/gui/qt/window.py:1076 msgid "No transactions to invalidate" msgstr "No hay transacciones que invalidar" -#: bal/gui/qt/window.py:1091 +#: bal/gui/qt/window.py:1094 msgid "Calculating Transactions" msgstr "Calculando transacciones" -#: bal/gui/qt/window.py:1107 +#: bal/gui/qt/window.py:1110 #, python-brace-format msgid "signed: {}" msgstr "firmadas: {}" -#: bal/gui/qt/window.py:1108 +#: bal/gui/qt/window.py:1111 #, python-brace-format msgid "signing: {}" msgstr "firmando: {}" -#: bal/gui/qt/window.py:1584 +#: bal/gui/qt/window.py:1588 msgid "Signing transactions..." msgstr "Firmando transacciones..." -#: bal/gui/qt/window.py:1598 +#: bal/gui/qt/window.py:1602 msgid "Some transaction was not broadcasted" msgstr "Alguna transacción no se transmitió" -#: bal/gui/qt/window.py:1602 +#: bal/gui/qt/window.py:1606 msgid "All transactions are broadcasted to respective Will-Executors" msgstr "Todas las transacciones se han transmitido a sus respectivos Will-Executor" -#: bal/gui/qt/window.py:1621 +#: bal/gui/qt/window.py:1625 msgid "Selecting Will-Executors" msgstr "Seleccionando Will-Executor" -#: bal/gui/qt/window.py:1636 +#: bal/gui/qt/window.py:1640 msgid "Broadcasting Transactions to Will-Executors:" msgstr "Transmitiendo transacciones a los Will-Executor:" -#: bal/gui/qt/window.py:1643 bal/gui/qt/window.py:2319 +#: bal/gui/qt/window.py:1647 bal/gui/qt/window.py:2323 msgid "waiting..." msgstr "esperando..." -#: bal/gui/qt/window.py:1659 +#: bal/gui/qt/window.py:1663 msgid "checking..." msgstr "comprobando..." -#: bal/gui/qt/window.py:1690 +#: bal/gui/qt/window.py:1694 #, python-brace-format msgid "checking {} - {} : {}" msgstr "comprobando {} - {} : {}" -#: bal/gui/qt/window.py:1699 +#: bal/gui/qt/window.py:1703 #, python-brace-format msgid "checked {} - {} : {}" msgstr "comprobado {} - {} : {}" -#: bal/gui/qt/window.py:1895 bal/gui/qt/window.py:1933 -#: bal/gui/qt/window.py:1957 +#: bal/gui/qt/window.py:1899 bal/gui/qt/window.py:1937 bal/gui/qt/window.py:1961 #, python-brace-format msgid "Invalid will file: {}" msgstr "Archivo de testamento no válido: {}" -#: bal/gui/qt/window.py:1914 bal/gui/qt/window.py:1965 +#: bal/gui/qt/window.py:1918 bal/gui/qt/window.py:1969 msgid "will" msgstr "testamento" -#: bal/gui/qt/window.py:2010 +#: bal/gui/qt/window.py:2014 msgid "Checking transactions" msgstr "Comprobando transacciones" -#: bal/gui/qt/window.py:2059 +#: bal/gui/qt/window.py:2063 msgid "Check Transaction" msgstr "Comprobar transacción" -#: bal/gui/qt/window.py:2168 +#: bal/gui/qt/window.py:2172 msgid "" "Could not download the will-executors list.\n" "\n" @@ -1936,7 +1930,7 @@ msgstr "" "\n" "Normalmente la causa es tu conexión a internet o un cortafuegos, no el plugin. Comprueba tu conexión (una VPN suele ayudar) e inténtalo de nuevo." -#: bal/gui/qt/window.py:2178 +#: bal/gui/qt/window.py:2182 msgid "" "Could not download the will-executors list over Tor.\n" "\n" @@ -1946,11 +1940,11 @@ msgstr "" "\n" "Electrum está conectado a través de Tor y la conexión tarda demasiado. Puede que tu conexión Tor sea lenta. Inténtalo de nuevo, o usa una VPN (o desactiva temporalmente Tor) para una conexión más rápida." -#: bal/gui/qt/window.py:2189 +#: bal/gui/qt/window.py:2193 msgid "Downloading will-executors list..." msgstr "Descargando la lista de Will-Executor..." -#: bal/gui/qt/window.py:2250 +#: bal/gui/qt/window.py:2254 #, python-brace-format msgid "" "No active will-executor servers found for the {chain} network.\n" @@ -1961,7 +1955,7 @@ msgstr "" "\n" "El servidor welist de {url} respondió pero no devolvió ningún Will-Executor para esta cadena." -#: bal/gui/qt/window.py:2258 +#: bal/gui/qt/window.py:2262 #, python-brace-format msgid "" "Could not reach the configured welist server.\n" @@ -1978,16 +1972,16 @@ msgstr "" "\n" "Comprueba la URL del servidor welist en la configuración del plugin." -#: bal/gui/qt/window.py:2268 +#: bal/gui/qt/window.py:2273 #, python-brace-format msgid "No active will-executor found for the {chain} network." msgstr "No se encontró ningún Will-Executor activo para la red {chain}." -#: bal/gui/qt/window.py:2305 +#: bal/gui/qt/window.py:2309 msgid "Ping Will-Executors:" msgstr "Ping a los Will-Executor:" -#: bal/gui/qt/window.py:2365 +#: bal/gui/qt/window.py:2369 msgid "Ping Will-Executors" msgstr "Ping a los Will-Executor" diff --git a/bal/locale/it_IT/LC_MESSAGES/bal.po b/bal/locale/it_IT/LC_MESSAGES/bal.po index f658edb..6039aaa 100644 --- a/bal/locale/it_IT/LC_MESSAGES/bal.po +++ b/bal/locale/it_IT/LC_MESSAGES/bal.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: BAL Electrum plugin VERSION\n" "Report-Msgid-Bugs-To: info@bitcoin-after.life\n" -"POT-Creation-Date: 2026-10-07 14:25-0400\n" +"POT-Creation-Date: 2026-10-10 15:53-0400\n" "PO-Revision-Date: 2026-09-26 22:02+0200\n" "Last-Translator: FULL NAME \n" "Language: it_IT\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.10.3\n" -#: bal/core/plugin_base.py:360 +#: bal/core/plugin_base.py:378 msgid "" "BAL will execution of $wallet_name\r\n" " heirs list: \r\n" @@ -28,105 +28,104 @@ msgstr "" " elenco eredi: \r\n" "$heirs_complete" -#: bal/core/plugin_base.py:366 +#: bal/core/plugin_base.py:384 msgid "BAL -Will execution of $wallet_name" msgstr "BAL - Esecuzione del testamento di $wallet_name" -#: bal/core/will.py:1317 +#: bal/core/will.py:1502 #, python-brace-format msgid "NOT {}" msgstr "NON {}" -#: bal/core/will.py:1341 +#: bal/core/will.py:1526 msgid "Anticipated" msgstr "Anticipato" -#: bal/core/will.py:1342 +#: bal/core/will.py:1527 msgid "Broadcasted" msgstr "Trasmesso" -#: bal/core/will.py:1343 +#: bal/core/will.py:1528 msgid "Checked" msgstr "Verificato" -#: bal/core/will.py:1344 +#: bal/core/will.py:1529 msgid "Check Failed" msgstr "Verifica fallita" -#: bal/core/will.py:1345 +#: bal/core/will.py:1530 msgid "Signed" msgstr "Firmato" -#: bal/core/will.py:1346 +#: bal/core/will.py:1531 msgid "Confirmed" msgstr "Confermato" -#: bal/core/will.py:1347 bal/gui/qt/lists.py:1354 bal/gui/qt/lists.py:1368 +#: bal/core/will.py:1532 bal/gui/qt/lists.py:1354 bal/gui/qt/lists.py:1368 msgid "Error" msgstr "Errore" -#: bal/core/will.py:1348 bal/gui/qt/dialogs.py:825 +#: bal/core/will.py:1533 bal/gui/qt/dialogs.py:826 msgid "Expired" msgstr "Scaduto" -#: bal/core/will.py:1349 +#: bal/core/will.py:1534 msgid "Exported" msgstr "Esportato" -#: bal/core/will.py:1350 +#: bal/core/will.py:1535 msgid "Imported" msgstr "Importato" -#: bal/core/will.py:1351 +#: bal/core/will.py:1536 msgid "Invalidated" msgstr "Invalidato" -#: bal/core/will.py:1352 +#: bal/core/will.py:1537 msgid "Mempool" msgstr "Mempool" -#: bal/core/will.py:1353 +#: bal/core/will.py:1538 msgid "Push failed" msgstr "Invio fallito" -#: bal/core/will.py:1354 +#: bal/core/will.py:1539 msgid "Pushed" msgstr "Inviato" -#: bal/core/will.py:1355 +#: bal/core/will.py:1540 msgid "Partially Signed" msgstr "Parzialmente firmato" -#: bal/core/will.py:1356 +#: bal/core/will.py:1541 msgid "Replaced" msgstr "Sostituito" -#: bal/core/will.py:1357 +#: bal/core/will.py:1542 msgid "Restored" msgstr "Ripristinato" -#: bal/core/will.py:1358 +#: bal/core/will.py:1543 msgid "Updated" msgstr "Aggiornato" -#: bal/core/will.py:1359 bal/gui/qt/dialogs.py:2633 +#: bal/core/will.py:1544 bal/gui/qt/dialogs.py:2635 msgid "Valid" msgstr "Valido" -#: bal/core/will.py:1365 bal/gui/qt/window.py:554 +#: bal/core/will.py:1550 msgid "New" msgstr "Nuovo" -#: bal/core/willexecutors.py:245 +#: bal/core/willexecutors.py:260 msgid "Waiting..." msgstr "In attesa..." -#: bal/core/willexecutors.py:379 bal/gui/qt/window.py:245 -#: bal/gui/qt/window.py:1663 +#: bal/core/willexecutors.py:394 bal/gui/qt/window.py:245 bal/gui/qt/window.py:1667 msgid "Success" msgstr "Riuscito" -#: bal/core/willexecutors.py:389 bal/gui/qt/window.py:1668 +#: bal/core/willexecutors.py:404 bal/gui/qt/window.py:1672 msgid "Failed" msgstr "Fallito" @@ -142,7 +141,7 @@ msgstr "Apri con..." msgid "Save" msgstr "Salva" -#: bal/gui/qt/calendar.py:72 bal/gui/qt/widgets.py:1043 +#: bal/gui/qt/calendar.py:73 bal/gui/qt/widgets.py:1044 msgid "No reminders were saved: the delivery date is too close (or already passed)" msgstr "Nessun promemoria salvato: la data di consegna è troppo vicina (o già passata)" @@ -207,15 +206,15 @@ msgstr "Il tuo {0} è stato esportato a '{1}'" msgid "Bal Wizard Setup" msgstr "Bal - Configurazione guidata" -#: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2762 +#: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2764 msgid "Previous" msgstr "Indietro" -#: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2765 +#: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2767 msgid "Next" msgstr "Avanti" -#: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4048 +#: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4050 msgid "Cancel" msgstr "Annulla" @@ -235,14 +234,11 @@ msgstr "" msgid "Add" msgstr "Aggiungi" -#: bal/gui/qt/dialogs.py:372 bal/gui/qt/dialogs.py:2594 bal/gui/qt/lists.py:311 -#: bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237 +#: bal/gui/qt/dialogs.py:372 bal/gui/qt/dialogs.py:2596 bal/gui/qt/lists.py:311 bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237 msgid "Import" msgstr "Importa" -#: bal/gui/qt/dialogs.py:374 bal/gui/qt/dialogs.py:2430 -#: bal/gui/qt/dialogs.py:2598 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672 -#: bal/gui/qt/lists.py:1249 +#: bal/gui/qt/dialogs.py:374 bal/gui/qt/dialogs.py:2432 bal/gui/qt/dialogs.py:2600 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672 bal/gui/qt/lists.py:1249 msgid "Export" msgstr "Esporta" @@ -294,25 +290,23 @@ msgstr "Preparazione del testamento" msgid "Building Will:" msgstr "Preparazione del testamento:" -#: bal/gui/qt/dialogs.py:731 bal/gui/qt/dialogs.py:756 -#: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:789 +#: bal/gui/qt/dialogs.py:731 bal/gui/qt/dialogs.py:757 bal/gui/qt/dialogs.py:767 bal/gui/qt/dialogs.py:790 msgid "Checking variables" msgstr "Controllo dei parametri" -#: bal/gui/qt/dialogs.py:757 +#: bal/gui/qt/dialogs.py:758 msgid "Check Alive Threshold Passed: you have to Invalidate your old Will" msgstr "La data della verifica se sei vivo è passata: devi invalidare il piano del vecchio testamento" -#: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:848 +#: bal/gui/qt/dialogs.py:767 bal/gui/qt/dialogs.py:850 msgid "No Heirs" msgstr "Nessun erede" -#: bal/gui/qt/dialogs.py:789 bal/gui/qt/dialogs.py:1336 -#: bal/gui/qt/dialogs.py:2282 bal/gui/qt/window.py:2315 +#: bal/gui/qt/dialogs.py:790 bal/gui/qt/dialogs.py:1338 bal/gui/qt/dialogs.py:2284 bal/gui/qt/window.py:2319 msgid "Ok" msgstr "OK" -#: bal/gui/qt/dialogs.py:793 +#: bal/gui/qt/dialogs.py:794 msgid "" "In the inheritance process, the entire wallet will always be fully emptied. \n" "Your settings require an adjustment of the amounts" @@ -320,109 +314,103 @@ msgstr "" "Nel processo di eredità il portafogli viene sempre svuotato del tutto. \n" "Le tue impostazioni richiedono una correzione degli importi" -#: bal/gui/qt/dialogs.py:802 bal/gui/qt/window.py:844 +#: bal/gui/qt/dialogs.py:803 bal/gui/qt/window.py:846 #, python-brace-format msgid "Will-executor fee too high: {}" msgstr "Commissione del Will-Executor troppo alta: {}" -#: bal/gui/qt/dialogs.py:836 +#: bal/gui/qt/dialogs.py:837 msgid "Postponed: invalidating old will" msgstr "Posticipato: invalidazione del piano del vecchio testamento" -#: bal/gui/qt/dialogs.py:871 bal/gui/qt/dialogs.py:880 -#: bal/gui/qt/dialogs.py:2242 +#: bal/gui/qt/dialogs.py:873 bal/gui/qt/dialogs.py:882 bal/gui/qt/dialogs.py:2244 msgid "Checking your will" msgstr "Controllo del testamento" -#: bal/gui/qt/dialogs.py:873 +#: bal/gui/qt/dialogs.py:875 msgid "An inheritance of this wallet is already executed (on blockchain)" msgstr "Un'eredità di questo portafogli è già stata eseguita (sulla blockchain)" -#: bal/gui/qt/dialogs.py:882 +#: bal/gui/qt/dialogs.py:884 msgid "Inheritance in mempool (waiting confirmation)" msgstr "Eredità nella mempool (in attesa di conferma)" -#: bal/gui/qt/dialogs.py:915 +#: bal/gui/qt/dialogs.py:917 msgid "Will-Executor excluded" msgstr "Will-Executor escluso" -#: bal/gui/qt/dialogs.py:915 bal/gui/qt/dialogs.py:1009 -#: bal/gui/qt/dialogs.py:2223 bal/gui/qt/dialogs.py:2234 +#: bal/gui/qt/dialogs.py:917 bal/gui/qt/dialogs.py:1011 bal/gui/qt/dialogs.py:2225 bal/gui/qt/dialogs.py:2236 msgid "Skipped" msgstr "Saltato" -#: bal/gui/qt/dialogs.py:921 bal/gui/qt/dialogs.py:1706 bal/gui/qt/lists.py:345 -#: bal/gui/qt/theme.py:116 +#: bal/gui/qt/dialogs.py:923 bal/gui/qt/dialogs.py:1708 bal/gui/qt/lists.py:345 bal/gui/qt/theme.py:116 msgid "Will-Executor" msgstr "Will-Executor" -#: bal/gui/qt/dialogs.py:922 +#: bal/gui/qt/dialogs.py:924 msgid "Not present - select one or enable backup mode" msgstr "Assente - selezionane uno o attiva la modalità 'transazione senza Will-Executor'" -#: bal/gui/qt/dialogs.py:983 +#: bal/gui/qt/dialogs.py:986 msgid "All heirs' shares are below the dust limit: the inheritance cannot be created. Increase the amounts or reduce the number of heirs." msgstr "Le quote di tutti gli eredi sono sotto il limite minimo (dust): l'eredità non può essere creata. Aumenta gli importi o riduci il numero di eredi." -#: bal/gui/qt/dialogs.py:999 +#: bal/gui/qt/dialogs.py:1002 #, python-brace-format msgid "Wallet balance is too low: {} satoshi available, but the miner and will-executor fees need {} satoshi (the minimum usable amount is {} satoshi). Add funds, or select fewer will-executors." msgstr "Il saldo del portafogli è troppo basso: {} satoshi disponibili, ma le commissioni dei miner e dei Will-Executor richiedono {} satoshi (l'importo minimo utilizzabile è {} satoshi). Aggiungi fondi, oppure seleziona meno Will-Executor." -#: bal/gui/qt/dialogs.py:1043 +#: bal/gui/qt/dialogs.py:1045 #, python-brace-format msgid "Heir {}" msgstr "Erede {}" -#: bal/gui/qt/dialogs.py:1045 +#: bal/gui/qt/dialogs.py:1047 #, python-brace-format msgid "{} is DUST - excluded (amount below dust limit)" msgstr "{} è DUST - escluso (importo sotto il limite minimo)" -#: bal/gui/qt/dialogs.py:1101 +#: bal/gui/qt/dialogs.py:1103 msgid "Heir" msgstr "Erede" -#: bal/gui/qt/dialogs.py:1218 bal/gui/qt/dialogs.py:2271 +#: bal/gui/qt/dialogs.py:1220 bal/gui/qt/dialogs.py:2273 #, python-brace-format msgid "Please wait {}secs" msgstr "Attendi {} secondi" -#: bal/gui/qt/dialogs.py:1225 bal/gui/qt/dialogs.py:1277 -#: bal/gui/qt/dialogs.py:1315 +#: bal/gui/qt/dialogs.py:1227 bal/gui/qt/dialogs.py:1279 bal/gui/qt/dialogs.py:1317 msgid "Broadcasting" msgstr "Trasmissione" -#: bal/gui/qt/dialogs.py:1336 bal/gui/qt/window.py:2317 +#: bal/gui/qt/dialogs.py:1338 bal/gui/qt/window.py:2321 msgid "Ko" msgstr "Ko" -#: bal/gui/qt/dialogs.py:1347 +#: bal/gui/qt/dialogs.py:1349 msgid "Timeout - no answer" msgstr "Tempo scaduto - nessuna risposta" -#: bal/gui/qt/dialogs.py:1389 +#: bal/gui/qt/dialogs.py:1391 #, python-brace-format msgid "checking {} - {}" msgstr "controllo {} - {}" -#: bal/gui/qt/dialogs.py:1392 bal/gui/qt/dialogs.py:2240 -#: bal/gui/qt/window.py:1691 +#: bal/gui/qt/dialogs.py:1394 bal/gui/qt/dialogs.py:2242 bal/gui/qt/window.py:1695 msgid "Waiting" msgstr "In attesa" -#: bal/gui/qt/dialogs.py:1406 +#: bal/gui/qt/dialogs.py:1408 #, python-brace-format msgid "checked {} - {}" msgstr "controllato {} - {}" -#: bal/gui/qt/dialogs.py:1456 bal/gui/qt/dialogs.py:2026 -#: bal/gui/qt/dialogs.py:2037 +#: bal/gui/qt/dialogs.py:1458 bal/gui/qt/dialogs.py:2028 bal/gui/qt/dialogs.py:2039 #, python-brace-format msgid "Error: {}" msgstr "Errore: {}" -#: bal/gui/qt/dialogs.py:1517 +#: bal/gui/qt/dialogs.py:1519 msgid "" "Your will has expired and must be invalidated before it can be rebuilt.\n" "A transaction window will now open:\n" @@ -434,11 +422,11 @@ msgstr "" "FIRMALA e poi TRASMETTILA per invalidare il piano del vecchio testamento.\n" "Quando l'invalidazione del piano è confermata, premi il pulsante Controlla per completare il testamento." -#: bal/gui/qt/dialogs.py:1546 bal/gui/qt/window.py:849 +#: bal/gui/qt/dialogs.py:1549 bal/gui/qt/window.py:852 msgid "Check Alive must be after today and before the delivery date, please update it" msgstr "La data della verifica se sei vivo deve essere dopo oggi e prima della data di consegna: aggiornala" -#: bal/gui/qt/dialogs.py:1569 +#: bal/gui/qt/dialogs.py:1571 msgid "" "Your old will has been invalidated and the transaction was broadcast.\n" "Electrum may need a little time to register it.\n" @@ -448,19 +436,19 @@ msgstr "" "Electrum potrebbe avere bisogno di un po' di tempo per registrarla.\n" "Attendi che la transazione di invalidazione sia confermata, poi premi di nuovo il pulsante Controlla per completare l'aggiornamento del testamento." -#: bal/gui/qt/dialogs.py:1585 +#: bal/gui/qt/dialogs.py:1587 msgid "Invalidate your old will" msgstr "Invalida il piano del vecchio testamento" -#: bal/gui/qt/dialogs.py:1599 bal/gui/qt/dialogs.py:1644 +#: bal/gui/qt/dialogs.py:1601 bal/gui/qt/dialogs.py:1646 msgid "Aborted" msgstr "Interrotto" -#: bal/gui/qt/dialogs.py:1618 +#: bal/gui/qt/dialogs.py:1620 msgid "Auto-sign disabled — sign manually" msgstr "Firma automatica disattivata — firma manualmente" -#: bal/gui/qt/dialogs.py:1631 +#: bal/gui/qt/dialogs.py:1633 msgid "" "The delivery date was automatically moved one day earlier so the updated will can correctly replace the previous one.\n" "Please sign (and broadcast) to confirm the change." @@ -468,134 +456,131 @@ msgstr "" "La data di consegna è stata anticipata automaticamente di un giorno, così il testamento aggiornato può sostituire correttamente il precedente.\n" "Firma (e trasmetti il piano) per confermare la modifica." -#: bal/gui/qt/dialogs.py:1641 +#: bal/gui/qt/dialogs.py:1643 msgid "Sign your will" msgstr "Firma il testamento" -#: bal/gui/qt/dialogs.py:1646 bal/gui/qt/dialogs.py:1929 +#: bal/gui/qt/dialogs.py:1648 bal/gui/qt/dialogs.py:1931 msgid "Nothing to do" msgstr "Niente da fare" -#: bal/gui/qt/dialogs.py:1665 +#: bal/gui/qt/dialogs.py:1667 msgid "Finished" msgstr "Finito" -#: bal/gui/qt/dialogs.py:1682 +#: bal/gui/qt/dialogs.py:1684 msgid "Calendar" msgstr "Calendario" -#: bal/gui/qt/dialogs.py:1684 bal/gui/qt/dialogs.py:1714 -#: bal/gui/qt/dialogs.py:1781 bal/gui/qt/dialogs.py:2029 -#: bal/gui/qt/dialogs.py:2976 bal/gui/qt/dialogs.py:3231 -#: bal/gui/qt/dialogs.py:4067 +#: bal/gui/qt/dialogs.py:1686 bal/gui/qt/dialogs.py:1716 bal/gui/qt/dialogs.py:1783 bal/gui/qt/dialogs.py:2031 bal/gui/qt/dialogs.py:2978 bal/gui/qt/dialogs.py:3233 bal/gui/qt/dialogs.py:4069 msgid "Close" msgstr "Chiudi" -#: bal/gui/qt/dialogs.py:1710 bal/gui/qt/dialogs.py:1777 +#: bal/gui/qt/dialogs.py:1712 bal/gui/qt/dialogs.py:1779 msgid "🔮 Wizard" msgstr "🔮 Procedura guidata" -#: bal/gui/qt/dialogs.py:1773 bal/gui/qt/dialogs.py:2581 +#: bal/gui/qt/dialogs.py:1775 bal/gui/qt/dialogs.py:2583 msgid "Heirs" msgstr "Eredi" -#: bal/gui/qt/dialogs.py:1882 bal/gui/qt/widgets.py:1150 +#: bal/gui/qt/dialogs.py:1884 bal/gui/qt/widgets.py:1150 #, python-brace-format msgid "(reminder {idx}/{total})" msgstr "(promemoria {idx}/{total})" -#: bal/gui/qt/dialogs.py:1946 +#: bal/gui/qt/dialogs.py:1948 msgid "All done" msgstr "Tutto fatto" -#: bal/gui/qt/dialogs.py:1997 +#: bal/gui/qt/dialogs.py:2000 msgid "Next steps (manual): press 'Sign' to sign your will, then 'Broadcast' to send it to the will-executors." msgstr "Prossimi passi (manuali): premi 'Firma' per firmare il testamento, poi 'Trasmetti il piano' per inviarlo ai Will-Executor." -#: bal/gui/qt/dialogs.py:2002 +#: bal/gui/qt/dialogs.py:2005 msgid "Next step (manual): press 'Sign' to sign your will." msgstr "Prossimo passo (manuale): premi 'Firma' per firmare il testamento." -#: bal/gui/qt/dialogs.py:2006 +#: bal/gui/qt/dialogs.py:2009 msgid "Next step (manual): press 'Broadcast' to send your will to the will-executors." msgstr "Prossimo passo (manuale): premi 'Trasmetti il piano' per inviare il testamento ai Will-Executor." -#: bal/gui/qt/dialogs.py:2101 +#: bal/gui/qt/dialogs.py:2104 #, python-brace-format msgid "Miner fee rate changed (the will was built with {} sat/byte, now it is {}): a new will must be prepared." msgstr "La commissione dei miner è cambiata (il testamento è stato creato con {} sat/byte, ora è {}): bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2106 +#: bal/gui/qt/dialogs.py:2109 msgid "The miner fee rate changed: a new will must be prepared." msgstr "La commissione dei miner è cambiata: bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2110 +#: bal/gui/qt/dialogs.py:2112 #, python-brace-format msgid "Will-executor \"{}\" is not covered by the current will: a new will must be prepared." msgstr "Il Will-Executor \"{}\" non è compreso nel testamento attuale: bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2115 +#: bal/gui/qt/dialogs.py:2118 msgid "Backup mode is enabled but the will has no backup transaction: a new will must be prepared." msgstr "La transazione senza Will-Executor (tx di backup) è attiva ma il testamento non ha la transazione scritta: bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2124 +#: bal/gui/qt/dialogs.py:2126 #, python-brace-format msgid "Heir \"{}\" is not covered by the current will (it was added or removed, or its delivery date changed): a new will must be prepared." msgstr "L'erede \"{}\" non è compreso nel testamento attuale (è stato aggiunto o rimosso, o la sua data di consegna è cambiata): bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2133 +#: bal/gui/qt/dialogs.py:2135 msgid "The heirs changed: a new will must be prepared." msgstr "Gli eredi sono cambiati: bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2135 +#: bal/gui/qt/dialogs.py:2137 msgid "A will-executor changed: a new will must be prepared." msgstr "Un Will-Executor è cambiato: bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2144 +#: bal/gui/qt/dialogs.py:2147 msgid "The wallet contains funds that the current will does not cover yet: a new will must be prepared." msgstr "Il portafogli contiene fondi che il testamento attuale non comprende ancora: bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2148 +#: bal/gui/qt/dialogs.py:2151 msgid "The will contains no valid transaction: a new will must be prepared." msgstr "Il testamento non contiene transazioni valide: bisogna preparare un nuovo testamento." -#: bal/gui/qt/dialogs.py:2154 +#: bal/gui/qt/dialogs.py:2157 msgid "The will is no longer coherent and must be rebuilt; the exact reason could not be determined." msgstr "Il testamento non è più coerente e va ricreato; non è stato possibile determinare il motivo esatto." -#: bal/gui/qt/dialogs.py:2184 +#: bal/gui/qt/dialogs.py:2187 msgid "No heirs: add at least one heir before building the will." msgstr "Nessun erede: aggiungi almeno un erede prima di preparare il testamento." -#: bal/gui/qt/dialogs.py:2187 +#: bal/gui/qt/dialogs.py:2190 msgid "The wallet has no spendable funds, so no inheritance transaction can be created." msgstr "Il portafogli non ha fondi spendibili, quindi non si può creare nessuna transazione di eredità." -#: bal/gui/qt/dialogs.py:2191 +#: bal/gui/qt/dialogs.py:2194 msgid "No usable will-executor: none of the servers in the list is both selected and valid. Open the will-executor settings, select at least one server and check that it is reachable." msgstr "Nessun Will-Executor utilizzabile: nessun server dell'elenco è sia selezionato sia valido. Apri le impostazioni dei Will-Executor, seleziona almeno un server e controlla che sia raggiungibile." -#: bal/gui/qt/dialogs.py:2196 +#: bal/gui/qt/dialogs.py:2199 msgid "No delivery date left to build: every heir's date is already covered by the existing will. Choose a later delivery date, or change an heir's date." msgstr "Nessuna data di consegna da preparare: la data di ogni erede è già compresa nel testamento esistente. Scegli una data di consegna successiva, oppure cambia la data di un erede." -#: bal/gui/qt/dialogs.py:2201 +#: bal/gui/qt/dialogs.py:2204 msgid "The amount to send must cover the miner fees plus this will-executor's fee, and the wallet balance is not enough: select cheaper will-executors, or add funds to the wallet." msgstr "L'importo da inviare deve coprire le commissioni dei miner più la commissione di questo Will-Executor, e il saldo del portafogli non basta: seleziona Will-Executor più economici, oppure aggiungi fondi al portafogli." -#: bal/gui/qt/dialogs.py:2206 +#: bal/gui/qt/dialogs.py:2209 msgid "A will-executor asks for more than the maximum fee you allowed: raise the maximum fee in the settings, or select a cheaper will-executor." msgstr "Un Will-Executor chiede più della commissione massima che hai permesso: alza la commissione massima nelle impostazioni, oppure seleziona un Will-Executor più economico." -#: bal/gui/qt/dialogs.py:2211 +#: bal/gui/qt/dialogs.py:2214 msgid "The inheritance transactions could not be assembled from the available funds (the balance may not cover the miner fees)." msgstr "Non è stato possibile comporre le transazioni di eredità con i fondi disponibili (il saldo potrebbe non coprire le commissioni dei miner)." -#: bal/gui/qt/dialogs.py:2215 +#: bal/gui/qt/dialogs.py:2218 msgid "An unexpected error stopped the transactions being prepared for a will-executor, so it was skipped. Try again, or select a different will-executor." msgstr "Un errore imprevisto ha interrotto la preparazione delle transazioni per un Will-Executor, che è stato saltato. Riprova, oppure seleziona un altro Will-Executor." -#: bal/gui/qt/dialogs.py:2226 +#: bal/gui/qt/dialogs.py:2228 msgid "" "Could not build the will, and the exact cause could not be determined. Please check that:\n" "- the wallet balance covers the miner and will-executor fees,\n" @@ -609,127 +594,126 @@ msgstr "" "- la data della verifica se sei vivo sia PRECEDENTE alla data di consegna,\n" "- almeno un Will-Executor sia selezionato e raggiungibile." -#: bal/gui/qt/dialogs.py:2247 +#: bal/gui/qt/dialogs.py:2249 msgid "Invalidating old will" msgstr "Invalidazione del piano del vecchio testamento" -#: bal/gui/qt/dialogs.py:2253 +#: bal/gui/qt/dialogs.py:2255 msgid "Building your will" msgstr "Preparazione del testamento" -#: bal/gui/qt/dialogs.py:2259 +#: bal/gui/qt/dialogs.py:2261 msgid "Signing your will" msgstr "Firma del testamento" -#: bal/gui/qt/dialogs.py:2265 +#: bal/gui/qt/dialogs.py:2267 msgid "Broadcasting your will to executors" msgstr "Trasmissione del piano ai Will-Executor" -#: bal/gui/qt/dialogs.py:2313 +#: bal/gui/qt/dialogs.py:2315 msgid "Wait" msgstr "Attendi" -#: bal/gui/qt/dialogs.py:2416 +#: bal/gui/qt/dialogs.py:2418 msgid "Will Details" msgstr "Dettagli del testamento" -#: bal/gui/qt/dialogs.py:2422 bal/gui/qt/dialogs.py:4042 -#: bal/gui/qt/lists.py:668 +#: bal/gui/qt/dialogs.py:2424 bal/gui/qt/dialogs.py:4044 bal/gui/qt/lists.py:668 msgid "Sign" msgstr "Firma" -#: bal/gui/qt/dialogs.py:2426 bal/gui/qt/lists.py:675 +#: bal/gui/qt/dialogs.py:2428 bal/gui/qt/lists.py:675 msgid "Broadcast" msgstr "Trasmissione" -#: bal/gui/qt/dialogs.py:2433 bal/gui/qt/lists.py:677 +#: bal/gui/qt/dialogs.py:2435 bal/gui/qt/lists.py:677 msgid "Invalidate" msgstr "Invalida il piano" -#: bal/gui/qt/dialogs.py:2438 bal/gui/qt/lists.py:674 +#: bal/gui/qt/dialogs.py:2440 bal/gui/qt/lists.py:674 msgid "Merge" msgstr "Unisci" -#: bal/gui/qt/dialogs.py:2446 +#: bal/gui/qt/dialogs.py:2448 #, python-brace-format msgid "Expiration date: {}" msgstr "Data di scadenza: {}" -#: bal/gui/qt/dialogs.py:2452 +#: bal/gui/qt/dialogs.py:2454 #, python-brace-format msgid "Valid Txs:{}" msgstr "Tx valide: {}" -#: bal/gui/qt/dialogs.py:2454 +#: bal/gui/qt/dialogs.py:2456 #, python-brace-format msgid "Total Txs:{}" msgstr "Tx totali: {}" -#: bal/gui/qt/dialogs.py:2508 +#: bal/gui/qt/dialogs.py:2510 msgid "Hide replaced" msgstr "Nascondi sostituite" -#: bal/gui/qt/dialogs.py:2510 +#: bal/gui/qt/dialogs.py:2512 msgid "Unhide replaced" msgstr "Mostra sostituite" -#: bal/gui/qt/dialogs.py:2516 +#: bal/gui/qt/dialogs.py:2518 msgid "Hide invalidated" msgstr "Nascondi invalidate" -#: bal/gui/qt/dialogs.py:2518 +#: bal/gui/qt/dialogs.py:2520 msgid "Unhide invalidated" msgstr "Mostra invalidate" -#: bal/gui/qt/dialogs.py:2543 +#: bal/gui/qt/dialogs.py:2545 msgid "Will-Executor Service List" msgstr "Elenco dei servizi Will-Executor" -#: bal/gui/qt/dialogs.py:2590 bal/gui/qt/lists.py:314 +#: bal/gui/qt/dialogs.py:2592 bal/gui/qt/lists.py:314 msgid "New Heir" msgstr "Nuovo erede" -#: bal/gui/qt/dialogs.py:2632 +#: bal/gui/qt/dialogs.py:2634 msgid "All" msgstr "Tutti" -#: bal/gui/qt/dialogs.py:2635 +#: bal/gui/qt/dialogs.py:2637 msgid "Valid NC" msgstr "Valido NC" -#: bal/gui/qt/dialogs.py:2773 bal/gui/qt/dialogs.py:2851 +#: bal/gui/qt/dialogs.py:2775 bal/gui/qt/dialogs.py:2853 msgid "Auto" msgstr "Auto" -#: bal/gui/qt/dialogs.py:2775 +#: bal/gui/qt/dialogs.py:2777 msgid "Automatically advance through the QR codes." msgstr "Scorri automaticamente i codici QR." -#: bal/gui/qt/dialogs.py:2779 +#: bal/gui/qt/dialogs.py:2781 msgid "QR codes per second:" msgstr "Codici QR al secondo:" -#: bal/gui/qt/dialogs.py:2783 +#: bal/gui/qt/dialogs.py:2785 msgid " /s" msgstr " /s" -#: bal/gui/qt/dialogs.py:2785 +#: bal/gui/qt/dialogs.py:2787 msgid "Loop" msgstr "Ripeti" -#: bal/gui/qt/dialogs.py:2787 +#: bal/gui/qt/dialogs.py:2790 msgid "When the last QR code is reached, keep cycling from the first one instead of stopping." msgstr "Arrivato all'ultimo codice QR, riparti dal primo invece di fermarti." -#: bal/gui/qt/dialogs.py:2797 +#: bal/gui/qt/dialogs.py:2799 msgid "QR code size:" msgstr "Dimensione del codice QR:" -#: bal/gui/qt/dialogs.py:2845 +#: bal/gui/qt/dialogs.py:2847 msgid "Stop" msgstr "Ferma" -#: bal/gui/qt/dialogs.py:2866 +#: bal/gui/qt/dialogs.py:2868 #, python-brace-format msgid "" "Scan the QR codes below with the will-opening device.\n" @@ -738,101 +722,98 @@ msgstr "" "Scansiona i codici QR qui sotto con il dispositivo che apre il testamento.\n" "{} codici contengono tutto il trasferimento (l'ordine non conta, i doppioni vengono ignorati)." -#: bal/gui/qt/dialogs.py:2888 +#: bal/gui/qt/dialogs.py:2890 #, python-brace-format msgid "Frame {} of {}" msgstr "Frame {} di {}" -#: bal/gui/qt/dialogs.py:2911 +#: bal/gui/qt/dialogs.py:2913 msgid "Export will" msgstr "Esporta il testamento" -#: bal/gui/qt/dialogs.py:2922 +#: bal/gui/qt/dialogs.py:2924 msgid "No will transaction to export." msgstr "Nessuna transazione del testamento da esportare." -#: bal/gui/qt/dialogs.py:2929 +#: bal/gui/qt/dialogs.py:2931 msgid "Export:" msgstr "Esporta:" -#: bal/gui/qt/dialogs.py:2936 +#: bal/gui/qt/dialogs.py:2938 msgid "Whole will" msgstr "Testamento completo" -#: bal/gui/qt/dialogs.py:2939 +#: bal/gui/qt/dialogs.py:2942 msgid "Export the full will items (all details/statuses) as JSON, or only the serialized transactions." msgstr "Esporta gli elementi completi del testamento (tutti i dettagli e gli stati) in JSON, oppure solo le transazioni serializzate." -#: bal/gui/qt/dialogs.py:2950 +#: bal/gui/qt/dialogs.py:2952 msgid "Send as:" msgstr "Invia come:" -#: bal/gui/qt/dialogs.py:2952 bal/gui/qt/dialogs.py:3208 +#: bal/gui/qt/dialogs.py:2954 bal/gui/qt/dialogs.py:3210 msgid "File" msgstr "File" -#: bal/gui/qt/dialogs.py:2953 bal/gui/qt/dialogs.py:3209 +#: bal/gui/qt/dialogs.py:2955 bal/gui/qt/dialogs.py:3211 msgid "QR Code" msgstr "Codice QR" -#: bal/gui/qt/dialogs.py:2954 bal/gui/qt/dialogs.py:3210 +#: bal/gui/qt/dialogs.py:2956 bal/gui/qt/dialogs.py:3212 msgid "Audio" msgstr "Audio" -#: bal/gui/qt/dialogs.py:3005 +#: bal/gui/qt/dialogs.py:3007 msgid "Export the will as a JSON file. Uncheck \"Whole will\" above to export only the serialized transactions (comma-separated)." msgstr "Esporta il testamento come file JSON. Togli la spunta a \"Testamento completo\" qui sopra per esportare solo le transazioni serializzate (separate da virgola)." -#: bal/gui/qt/dialogs.py:3014 +#: bal/gui/qt/dialogs.py:3016 msgid "Export…" msgstr "Esporta…" -#: bal/gui/qt/dialogs.py:3032 bal/gui/qt/dialogs.py:3271 -#: bal/gui/qt/window.py:1787 bal/gui/qt/window.py:1800 +#: bal/gui/qt/dialogs.py:3034 bal/gui/qt/dialogs.py:3273 bal/gui/qt/window.py:1791 bal/gui/qt/window.py:1804 msgid "Audio MODEM plugin is not available." msgstr "Il plugin Audio MODEM non è disponibile." -#: bal/gui/qt/dialogs.py:3036 bal/gui/qt/dialogs.py:3275 +#: bal/gui/qt/dialogs.py:3038 bal/gui/qt/dialogs.py:3277 msgid "Speed (KB/sec):" msgstr "Velocità (KB/sec):" -#: bal/gui/qt/dialogs.py:3047 +#: bal/gui/qt/dialogs.py:3049 msgid "Send" msgstr "Invia" -#: bal/gui/qt/dialogs.py:3100 bal/gui/qt/dialogs.py:3134 -#: bal/gui/qt/dialogs.py:3159 +#: bal/gui/qt/dialogs.py:3102 bal/gui/qt/dialogs.py:3136 bal/gui/qt/dialogs.py:3161 msgid "No will transaction matches the selected filter." msgstr "Nessuna transazione del testamento corrisponde al filtro selezionato." -#: bal/gui/qt/dialogs.py:3123 +#: bal/gui/qt/dialogs.py:3125 msgid "item(s)" msgstr "elemento/i" -#: bal/gui/qt/dialogs.py:3123 +#: bal/gui/qt/dialogs.py:3125 msgid "transaction(s)" msgstr "transazione/i" -#: bal/gui/qt/dialogs.py:3125 +#: bal/gui/qt/dialogs.py:3127 #, python-brace-format msgid "{} {} will be exported." msgstr "Verranno esportati: {} {}." -#: bal/gui/qt/dialogs.py:3128 +#: bal/gui/qt/dialogs.py:3130 #, python-brace-format msgid "{} {} will be sent." msgstr "Verranno inviati: {} {}." -#: bal/gui/qt/dialogs.py:3152 bal/gui/qt/dialogs.py:3155 -#: bal/gui/qt/dialogs.py:3311 bal/gui/qt/dialogs.py:3314 +#: bal/gui/qt/dialogs.py:3154 bal/gui/qt/dialogs.py:3157 bal/gui/qt/dialogs.py:3313 bal/gui/qt/dialogs.py:3316 msgid "Invalid audio speed." msgstr "Velocità audio non valida." -#: bal/gui/qt/dialogs.py:3191 +#: bal/gui/qt/dialogs.py:3193 msgid "Import will" msgstr "Importa il testamento" -#: bal/gui/qt/dialogs.py:3197 +#: bal/gui/qt/dialogs.py:3199 msgid "" "Choose how the will was exported: from a file, QR codes or audio.\n" "The import never touches the live will." @@ -840,59 +821,59 @@ msgstr "" "Scegli come è stato esportato il testamento: da file, codici QR o audio.\n" "L'importazione non modifica mai il testamento in uso." -#: bal/gui/qt/dialogs.py:3206 +#: bal/gui/qt/dialogs.py:3208 msgid "Import from:" msgstr "Importa da:" -#: bal/gui/qt/dialogs.py:3246 +#: bal/gui/qt/dialogs.py:3249 msgid "Import the JSON will file written by the Export ▶ File option. The will opens in a read-only preview window." msgstr "Importa il file JSON del testamento creato con l'opzione Esporta ▶ File. Il testamento si apre in una finestra di anteprima in sola lettura." -#: bal/gui/qt/dialogs.py:3253 +#: bal/gui/qt/dialogs.py:3255 msgid "Choose will file…" msgstr "Scegli il file del testamento…" -#: bal/gui/qt/dialogs.py:3284 +#: bal/gui/qt/dialogs.py:3286 msgid "Waiting for the audio transfer…" msgstr "In attesa del trasferimento audio…" -#: bal/gui/qt/dialogs.py:3287 +#: bal/gui/qt/dialogs.py:3289 msgid "Receive by audio…" msgstr "Ricevi via audio…" -#: bal/gui/qt/dialogs.py:3328 +#: bal/gui/qt/dialogs.py:3330 msgid "Receiving…" msgstr "Ricezione in corso…" -#: bal/gui/qt/dialogs.py:3348 +#: bal/gui/qt/dialogs.py:3350 msgid "No transaction data received." msgstr "Nessun dato di transazione ricevuto." -#: bal/gui/qt/dialogs.py:3366 +#: bal/gui/qt/dialogs.py:3368 #, python-brace-format msgid "Waiting for audio ({:.1f} kbps)…" msgstr "In attesa dell'audio ({:.1f} kbps)…" -#: bal/gui/qt/dialogs.py:3439 +#: bal/gui/qt/dialogs.py:3441 #, python-brace-format msgid "Could not parse a transferred will item: {}" msgstr "Impossibile leggere un elemento del testamento trasferito: {}" -#: bal/gui/qt/dialogs.py:3459 +#: bal/gui/qt/dialogs.py:3461 #, python-brace-format msgid "Could not parse a transferred transaction: {}" msgstr "Impossibile leggere una transazione trasferita: {}" -#: bal/gui/qt/dialogs.py:3470 +#: bal/gui/qt/dialogs.py:3473 msgid "The imported will contains no valid transaction in this wallet." msgstr "Il testamento importato non contiene transazioni valide in questo portafogli." -#: bal/gui/qt/dialogs.py:3481 +#: bal/gui/qt/dialogs.py:3484 #, python-brace-format msgid "{} imported transaction(s) are not valid in this wallet and were skipped." msgstr "{} transazione/i importata/e non valide in questo portafogli, quindi saltate." -#: bal/gui/qt/dialogs.py:3581 +#: bal/gui/qt/dialogs.py:3583 msgid "" "Show the will QR codes to the camera, one after another.\n" "After the first code, every new code is captured automatically.\n" @@ -902,114 +883,114 @@ msgstr "" "Dopo il primo codice, ogni nuovo codice viene acquisito automaticamente.\n" "Il primo frame indica il numero totale di codici; i doppioni vengono ignorati." -#: bal/gui/qt/dialogs.py:3591 bal/gui/qt/dialogs.py:3769 +#: bal/gui/qt/dialogs.py:3593 bal/gui/qt/dialogs.py:3771 msgid "Waiting for the first frame…" msgstr "In attesa del primo frame…" -#: bal/gui/qt/dialogs.py:3608 +#: bal/gui/qt/dialogs.py:3610 msgid "…or paste/type the frame text here" msgstr "…oppure incolla o scrivi qui il testo del frame" -#: bal/gui/qt/dialogs.py:3612 +#: bal/gui/qt/dialogs.py:3614 msgid "Add frame" msgstr "Aggiungi frame" -#: bal/gui/qt/dialogs.py:3618 bal/gui/qt/dialogs.py:3861 +#: bal/gui/qt/dialogs.py:3620 bal/gui/qt/dialogs.py:3863 msgid "Scan with camera…" msgstr "Scansiona con la fotocamera…" -#: bal/gui/qt/dialogs.py:3620 +#: bal/gui/qt/dialogs.py:3623 msgid "Start the live camera. While it runs, every new QR code shown to the camera is captured automatically." msgstr "Avvia la fotocamera. Mentre è attiva, ogni nuovo codice QR mostrato viene acquisito automaticamente." -#: bal/gui/qt/dialogs.py:3627 +#: bal/gui/qt/dialogs.py:3629 msgid "Reset" msgstr "Reimposta" -#: bal/gui/qt/dialogs.py:3634 +#: bal/gui/qt/dialogs.py:3636 msgid "Review and Sign…" msgstr "Controlla e firma…" -#: bal/gui/qt/dialogs.py:3653 +#: bal/gui/qt/dialogs.py:3656 #, python-brace-format msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; scan the first code again." msgstr "Il codice scansionato appartiene a un altro trasferimento ({} frame, {}). L'importazione è stata azzerata; scansiona di nuovo il primo codice." -#: bal/gui/qt/dialogs.py:3744 +#: bal/gui/qt/dialogs.py:3746 #, python-brace-format msgid "All {} frames stored." msgstr "Tutti i {} frame salvati." -#: bal/gui/qt/dialogs.py:3750 +#: bal/gui/qt/dialogs.py:3752 #, python-brace-format msgid "Stored {} of {} frames." msgstr "Salvati {} frame su {}." -#: bal/gui/qt/dialogs.py:3815 +#: bal/gui/qt/dialogs.py:3817 msgid "Cannot start QR scanner, no usable camera found." msgstr "Impossibile avviare lo scanner QR, nessuna fotocamera utilizzabile trovata." -#: bal/gui/qt/dialogs.py:3820 +#: bal/gui/qt/dialogs.py:3822 msgid "Stop scanning" msgstr "Ferma la scansione" -#: bal/gui/qt/dialogs.py:3867 +#: bal/gui/qt/dialogs.py:3869 #, python-brace-format msgid "Camera error: {}" msgstr "Errore della fotocamera: {}" -#: bal/gui/qt/dialogs.py:3913 +#: bal/gui/qt/dialogs.py:3916 #, python-brace-format msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; show the first code again." msgstr "Il codice scansionato appartiene a un altro trasferimento ({} frame, {}). L'importazione è stata azzerata; mostra di nuovo il primo codice." -#: bal/gui/qt/dialogs.py:3957 +#: bal/gui/qt/dialogs.py:3959 msgid "The transferred will contains no transactions." msgstr "Il testamento trasferito non contiene transazioni." -#: bal/gui/qt/dialogs.py:3986 +#: bal/gui/qt/dialogs.py:3988 msgid "Review and sign imported will" msgstr "Controlla e firma il testamento importato" -#: bal/gui/qt/dialogs.py:3998 +#: bal/gui/qt/dialogs.py:4001 msgid "Enter your wallet password to sign the imported transactions." msgstr "Inserisci la password del portafogli per firmare le transazioni importate." -#: bal/gui/qt/dialogs.py:4045 +#: bal/gui/qt/dialogs.py:4047 msgid "Skip" msgstr "Salta" -#: bal/gui/qt/dialogs.py:4061 +#: bal/gui/qt/dialogs.py:4063 msgid "Save signed file…" msgstr "Salva il file firmato…" -#: bal/gui/qt/dialogs.py:4064 +#: bal/gui/qt/dialogs.py:4066 msgid "Show signed QR…" msgstr "Mostra i QR firmati…" -#: bal/gui/qt/dialogs.py:4088 +#: bal/gui/qt/dialogs.py:4090 #, python-brace-format msgid "Transaction {} of {}" msgstr "Transazione {} di {}" -#: bal/gui/qt/dialogs.py:4090 +#: bal/gui/qt/dialogs.py:4092 #, python-brace-format msgid "TXID: {}" msgstr "TXID: {}" -#: bal/gui/qt/dialogs.py:4093 +#: bal/gui/qt/dialogs.py:4095 msgid "unknown" msgstr "sconosciuto" -#: bal/gui/qt/dialogs.py:4099 +#: bal/gui/qt/dialogs.py:4101 msgid "(no outputs)" msgstr "(nessun output)" -#: bal/gui/qt/dialogs.py:4113 +#: bal/gui/qt/dialogs.py:4115 msgid "unknown (partial transaction)" msgstr "sconosciuta (transazione parziale)" -#: bal/gui/qt/dialogs.py:4115 +#: bal/gui/qt/dialogs.py:4117 #, python-brace-format msgid "" "Total outputs: {}\n" @@ -1018,7 +999,7 @@ msgstr "" "Totale output: {}\n" "Commissione: {}" -#: bal/gui/qt/dialogs.py:4126 +#: bal/gui/qt/dialogs.py:4128 #, python-brace-format msgid "" "Signed {} of {} transactions.\n" @@ -1029,16 +1010,16 @@ msgstr "" "\n" "Salva un file firmato da portare sul dispositivo che trasmette il piano, oppure mostra le transazioni firmate come codici QR." -#: bal/gui/qt/dialogs.py:4145 +#: bal/gui/qt/dialogs.py:4147 #, python-brace-format msgid "Could not sign the transaction: {}" msgstr "Impossibile firmare la transazione: {}" -#: bal/gui/qt/dialogs.py:4164 +#: bal/gui/qt/dialogs.py:4166 msgid "Signed will saved." msgstr "Testamento firmato salvato." -#: bal/gui/qt/dialogs.py:4175 +#: bal/gui/qt/dialogs.py:4177 msgid "No signed transaction to show." msgstr "Nessuna transazione firmata da mostrare." @@ -1046,8 +1027,7 @@ msgstr "Nessuna transazione firmata da mostrare." msgid "Name" msgstr "Nome" -#: bal/gui/qt/lists.py:135 bal/gui/qt/lists.py:1432 bal/gui/qt/widgets.py:1420 -#: bal/gui/qt/window.py:374 +#: bal/gui/qt/lists.py:135 bal/gui/qt/lists.py:1432 bal/gui/qt/widgets.py:1420 bal/gui/qt/window.py:374 msgid "Address" msgstr "Indirizzo" @@ -1255,8 +1235,7 @@ msgstr "Deseleziona solo i non validi" msgid "Edit: {}" msgstr "Modifica: {}" -#: bal/gui/qt/lists.py:1290 bal/gui/qt/lists.py:1302 bal/gui/qt/lists.py:1316 -#: bal/gui/qt/lists.py:1476 +#: bal/gui/qt/lists.py:1290 bal/gui/qt/lists.py:1302 bal/gui/qt/lists.py:1316 bal/gui/qt/lists.py:1476 msgid "New Will Executor" msgstr "Nuovo Will-Executor" @@ -1310,67 +1289,67 @@ msgstr "Nessun Will-Executor corrisponde al filtro selezionato" msgid "No will-executor is selected" msgstr "Nessun Will-Executor selezionato" -#: bal/gui/qt/plugin.py:139 +#: bal/gui/qt/plugin.py:141 msgid "Electrum Plugins" msgstr "Plugin Electrum" -#: bal/gui/qt/plugin.py:306 +#: bal/gui/qt/plugin.py:308 msgid "Bitcoin After Life" msgstr "Bitcoin After Life" -#: bal/gui/qt/plugin.py:425 bal/gui/qt/plugin.py:450 +#: bal/gui/qt/plugin.py:427 bal/gui/qt/plugin.py:452 msgid "Settings" msgstr "Impostazioni" -#: bal/gui/qt/plugin.py:551 +#: bal/gui/qt/plugin.py:553 msgid "BASIC" msgstr "BASE" -#: bal/gui/qt/plugin.py:551 +#: bal/gui/qt/plugin.py:553 msgid "ADVANCED" msgstr "AVANZATO" -#: bal/gui/qt/plugin.py:571 +#: bal/gui/qt/plugin.py:573 msgid "Enable ADVANCED mode" msgstr "Attiva la modalità AVANZATA" -#: bal/gui/qt/plugin.py:572 +#: bal/gui/qt/plugin.py:574 msgid "Type 'at My Risk' to enable ADVANCED mode" msgstr "Scrivi 'at My Risk' per attivare la modalità AVANZATA" -#: bal/gui/qt/plugin.py:641 +#: bal/gui/qt/plugin.py:645 bal/gui/qt/plugin.py:983 msgid "Reset to default" msgstr "Ripristina il valore predefinito" -#: bal/gui/qt/plugin.py:659 +#: bal/gui/qt/plugin.py:663 msgid "Rebroadcast transactions" msgstr "Trasmetti di nuovo il piano" -#: bal/gui/qt/plugin.py:670 +#: bal/gui/qt/plugin.py:674 msgid "Warning: change these settings only if you know what you are doing." msgstr "Attenzione: cambia queste impostazioni solo se sai cosa stai facendo." -#: bal/gui/qt/plugin.py:685 +#: bal/gui/qt/plugin.py:689 msgid "Hide Replaced" msgstr "Nascondi sostituite" -#: bal/gui/qt/plugin.py:688 +#: bal/gui/qt/plugin.py:692 msgid "Hide replaced transactions from will detail and list" msgstr "Nascondi le transazioni sostituite dal dettaglio e dall'elenco del testamento" -#: bal/gui/qt/plugin.py:693 +#: bal/gui/qt/plugin.py:697 msgid "Hide Invalidated" msgstr "Nascondi invalidate" -#: bal/gui/qt/plugin.py:696 +#: bal/gui/qt/plugin.py:700 msgid "Hide invalidated transactions from will detail and list" msgstr "Nascondi le transazioni invalidate dal dettaglio e dall'elenco del testamento" -#: bal/gui/qt/plugin.py:699 +#: bal/gui/qt/plugin.py:703 msgid "Auto-sign on Check" msgstr "Firma automatica al Controllo" -#: bal/gui/qt/plugin.py:700 +#: bal/gui/qt/plugin.py:702 msgid "" "When checking, automatically sign and broadcast the will transactions to their will-executors.\n" "The wallet password is requested only if the wallet is encrypted." @@ -1378,11 +1357,11 @@ msgstr "" "Durante il controllo, firma automaticamente le transazioni del testamento e trasmetti il piano ai rispettivi Will-Executor.\n" "La password del portafogli viene chiesta solo se il portafogli è cifrato." -#: bal/gui/qt/plugin.py:715 +#: bal/gui/qt/plugin.py:719 msgid "Panel editable Date and Fee" msgstr "Data e commissione modificabili nel pannello" -#: bal/gui/qt/plugin.py:718 +#: bal/gui/qt/plugin.py:720 msgid "" "When enabled, the delivery-time and check-alive date fields can be edited everywhere (toolbar / Heirs tab), not only in the will-building wizard.\n" "When disabled, those dates are display-only outside the wizard." @@ -1390,11 +1369,11 @@ msgstr "" "Se attivo, i campi della data di consegna e della data della verifica se sei vivo si possono modificare ovunque (barra degli strumenti / scheda Eredi), non solo nella procedura guidata del testamento.\n" "Se disattivo, fuori dalla procedura guidata queste date si possono solo vedere." -#: bal/gui/qt/plugin.py:730 +#: bal/gui/qt/plugin.py:734 msgid "Max Will-Executor Fee (satoshi)" msgstr "Commissione massima del Will-Executor (satoshi)" -#: bal/gui/qt/plugin.py:733 +#: bal/gui/qt/plugin.py:735 msgid "" "Maximum fee (in satoshi) allowed to be paid to a single will-executor. If a will-executor charges more than this, the will will not be built.\n" "Default: 500,000 satoshi (0.005 BTC)." @@ -1402,11 +1381,11 @@ msgstr "" "Commissione massima (in satoshi) che si può pagare a un singolo Will-Executor. Se un Will-Executor chiede di più, il testamento non viene preparato.\n" "Predefinito: 500.000 satoshi (0,005 BTC)." -#: bal/gui/qt/plugin.py:745 +#: bal/gui/qt/plugin.py:749 msgid "User Type" msgstr "Tipo di utente" -#: bal/gui/qt/plugin.py:748 +#: bal/gui/qt/plugin.py:750 msgid "" "Choose how much detail the plugin shows.\n" "\n" @@ -1420,19 +1399,19 @@ msgstr "" "\n" "AVANZATO: mostra tutti i controlli, compresi il campo 'Verifica se sei vivo' e il selettore Raw/Data. Permette di configurare l'URL del server welist e lascia sempre modificabili tutti i campi di data e commissione." -#: bal/gui/qt/plugin.py:762 +#: bal/gui/qt/plugin.py:766 msgid "Number of reminders" msgstr "Numero di promemoria" -#: bal/gui/qt/plugin.py:763 +#: bal/gui/qt/plugin.py:768 msgid "How many reminder alarms the exported calendar (.ics) event contains. Range: 1 to 5 (default 3). Only used in ADVANCED mode." msgstr "Quanti promemoria contiene l'evento del calendario esportato (.ics). Da 1 a 5 (predefinito 3). Usato solo in modalità AVANZATA." -#: bal/gui/qt/plugin.py:773 +#: bal/gui/qt/plugin.py:777 msgid "Event summary" msgstr "Titolo dell'evento" -#: bal/gui/qt/plugin.py:774 +#: bal/gui/qt/plugin.py:776 msgid "" "Default message to be used in event summary\n" "Variables:\n" @@ -1446,11 +1425,11 @@ msgstr "" " $heirs_complete: elenco degli eredi con nome, indirizzo, importo\n" "Usato solo in modalità AVANZATA." -#: bal/gui/qt/plugin.py:787 +#: bal/gui/qt/plugin.py:791 msgid "Event description" msgstr "Descrizione dell'evento" -#: bal/gui/qt/plugin.py:788 +#: bal/gui/qt/plugin.py:790 msgid "" "Default message to be used in event description\n" "Variables:\n" @@ -1464,19 +1443,19 @@ msgstr "" " $heirs_complete: elenco degli eredi con nome, indirizzo, importo\n" "Usato solo in modalità AVANZATA." -#: bal/gui/qt/plugin.py:802 +#: bal/gui/qt/plugin.py:806 msgid "Welist Server URL" msgstr "URL del server welist" -#: bal/gui/qt/plugin.py:803 +#: bal/gui/qt/plugin.py:808 msgid "URL of the server that provides the will-executor list. Only available in ADVANCED mode." msgstr "URL del server che fornisce l'elenco dei Will-Executor. Disponibile solo in modalità AVANZATA." -#: bal/gui/qt/plugin.py:813 +#: bal/gui/qt/plugin.py:817 msgid "Calendar app command" msgstr "Comando dell'app calendario" -#: bal/gui/qt/plugin.py:814 +#: bal/gui/qt/plugin.py:816 msgid "" "Command used to open .ics calendar files.\n" "Leave empty to use the system default (xdg-open/open/start).\n" @@ -1486,11 +1465,11 @@ msgstr "" "Lascia vuoto per usare quello predefinito del sistema (xdg-open/open/start).\n" "Usato solo in modalità AVANZATA." -#: bal/gui/qt/plugin.py:828 +#: bal/gui/qt/plugin.py:832 msgid "Save inheritance transactions in history" msgstr "Salva le transazioni di eredità nella cronologia" -#: bal/gui/qt/plugin.py:829 +#: bal/gui/qt/plugin.py:831 #, python-brace-format msgid "" "After each check, save the valid will transactions into the wallet's local history (the History tab), each with a label.\n" @@ -1503,11 +1482,11 @@ msgstr "" " {willexecutor}: sostituita con l'URL del Will-Executor dell'elemento\n" "Usato solo in modalità AVANZATA." -#: bal/gui/qt/plugin.py:842 +#: bal/gui/qt/plugin.py:846 msgid "History label" msgstr "Etichetta nella cronologia" -#: bal/gui/qt/plugin.py:843 +#: bal/gui/qt/plugin.py:845 #, python-brace-format msgid "" "Label applied to the will transactions saved into the wallet's local history.\n" @@ -1520,15 +1499,15 @@ msgstr "" " {willexecutor}: sostituita con l'URL del Will-Executor dell'elemento\n" "Usato solo in modalità AVANZATA." -#: bal/gui/qt/plugin.py:866 +#: bal/gui/qt/plugin.py:870 msgid "Broadcast all transactions to willexecutors including those already pushed" msgstr "Trasmetti il piano ai Will-Executor con tutte le transazioni, comprese quelle già inviate" -#: bal/gui/qt/plugin.py:875 +#: bal/gui/qt/plugin.py:879 msgid "Rebuild will on wallet close" msgstr "Ricrea il testamento alla chiusura del portafogli" -#: bal/gui/qt/plugin.py:876 +#: bal/gui/qt/plugin.py:878 msgid "" "Run the 'Build your will' wizard every time the wallet is closed or Electrum is quit, so the will is rebuilt and re-validated.\n" "When disabled, the will is only rebuilt when you press Check or Prepare. The last built state is still saved to the wallet." @@ -1536,11 +1515,11 @@ msgstr "" "Avvia la procedura 'Crea il tuo testamento' ogni volta che il portafogli viene chiuso o si esce da Electrum, così il testamento viene ricreato e riconvalidato.\n" "Se disattivo, il testamento viene ricreato solo quando premi Controlla o Prepara. L'ultimo stato preparato viene comunque salvato nel portafogli." -#: bal/gui/qt/plugin.py:893 +#: bal/gui/qt/plugin.py:897 msgid "Rebuild automatically on new transactions" msgstr "Ricrea automaticamente con nuove transazioni" -#: bal/gui/qt/plugin.py:894 +#: bal/gui/qt/plugin.py:896 msgid "" "When a new transaction arrives for the wallet, automatically rebuild the will the same way the wizard does at wallet close: the delivery date is anticipated by one day so the new will replaces the previous one, and the rebuilt transactions are signed and sent to their will-executors.\n" "An on-chain invalidation transaction is only built when the anticipated delivery date would fall before the Check Alive threshold, or when the threshold is already in the past.\n" @@ -1550,23 +1529,39 @@ msgstr "" "Una transazione di invalidazione del piano sulla blockchain viene creata solo se la data di consegna anticipata cadrebbe prima della data della verifica se sei vivo, o se quella data è già passata.\n" "Se disattivo (predefinito), il testamento viene ricreato solo con Controlla / Prepara / chiusura del portafogli." -#: bal/gui/qt/plugin.py:976 +#: bal/gui/qt/plugin.py:970 +#, fuzzy +msgid "Invalidation fee policy" +msgstr "Invalidazione del piano del vecchio testamento" + +#: bal/gui/qt/plugin.py:966 +msgid "" +"Fee rate the on-chain invalidation transaction pays.\n" +"The invalidation is sent TODAY (it spends the will's coins so the old inheritance transactions become unusable), so it should pay the current network fee - not the will's mining fee rate.\n" +"\n" +"Feerate: a fixed rate (sat/vByte).\n" +"ETA: fee based on the estimated confirmation time (blocks).\n" +"Mempool: fee targeting a depth in the memory pool.\n" +"Only used in ADVANCED mode." +msgstr "" + +#: bal/gui/qt/plugin.py:1054 msgid "Reset to Default Setting" msgstr "Ripristina le impostazioni predefinite" -#: bal/gui/qt/plugin.py:977 +#: bal/gui/qt/plugin.py:1055 msgid "Reset these settings to their default values" msgstr "Riporta queste impostazioni ai valori predefiniti" -#: bal/gui/qt/plugin.py:984 +#: bal/gui/qt/plugin.py:1062 msgid "Open the Bitcoin After Life support website" msgstr "Apri il sito di supporto di Bitcoin After Life" -#: bal/gui/qt/plugin.py:996 +#: bal/gui/qt/plugin.py:1074 msgid "Support:" msgstr "Supporto:" -#: bal/gui/qt/plugin.py:1053 +#: bal/gui/qt/plugin.py:1131 #, python-brace-format msgid "BAL - {}" msgstr "BAL - {}" @@ -1633,7 +1628,7 @@ msgstr "Raw" msgid "Date" msgstr "Data" -#: bal/gui/qt/widgets.py:645 +#: bal/gui/qt/widgets.py:646 msgid "CHECK ALIVE

In DATA mode:
set the date for the “check alive” parameter.
When you open the wallet, if the “check alive” date has passed, the plugin will ask if you want to postpone the inheritance, since it assumes you still have control of the wallet and that you are still alive.

In RAW mode:
When less than this time is missing, ask to invalidate.
If you fail to invalidate during this time, your transactions will be delivered to your heirs.

if you choose Raw, you can insert various options based on suffix:
- d: number of days after current day(ex: 1d means tomorrow)
- y: number of years after current day(ex: 1y means one year from today)
The sign selects the reference date:
- a NEGATIVE value (ex: -30d) counts backwards from the delivery date (30 days before the delivery);
- a POSITIVE value (ex: 30d) counts forward from the inheritance creation date (30 days after the will was created).

Note: the date/time is expressed in your computer's Local time (not UTC time).
" msgstr "VERIFICA SE SEI VIVO

In modalità DATA:
imposta la data della “verifica se sei vivo”.
Quando apri il portafogli, se quella data è passata, il plugin ti chiede se vuoi posticipare l'eredità, perché presume che tu abbia ancora il controllo del portafogli e che tu sia ancora vivo.

In modalità RAW:
quando manca meno di questo tempo, chiede di invalidare il piano.
Se non invalidi il piano entro questo tempo, le tue transazioni verranno consegnate ai tuoi eredi.

se scegli Raw, puoi inserire diverse opzioni in base al suffisso:
- d: numero di giorni dopo oggi (es.: 1d significa domani)
- y: numero di anni dopo oggi (es.: 1y significa tra un anno)
Il segno seleziona la data di riferimento:
- un valore NEGATIVO (es.: -30d) conta all'indietro a partire dalla data di consegna (30 giorni prima della consegna);
- un valore POSITIVO (es.: 30d) conta in avanti a partire dalla data di creazione dell'eredità (30 giorni dopo la creazione del testamento).

Nota: la data e l'ora sono espresse nell'ora locale del tuo computer (non in ora UTC).
" @@ -1641,7 +1636,7 @@ msgstr "VERIFICA SE SEI VIVO

In modalità DATA:
imposta la data msgid "Check Alive" msgstr "Verifica se sei vivo" -#: bal/gui/qt/widgets.py:686 +#: bal/gui/qt/widgets.py:687 msgid "DELIVERY TIME

Set Locktime for transactions.
Any time is needed transaction will be anticipated by 1day

(ONLY IN ADVANCED MODE)
if you choose Raw, you can insert various options based on suffix:
- d: number of days after current day(ex: 1d means tomorrow)
- y: number of years after currrent day(ex: 1y means one year from today)

Note: the date/time is expressed in your computer's Local time (not UTC time).
" msgstr "DATA DI CONSEGNA

Imposta il locktime delle transazioni.
Ogni volta che serve, la transazione viene anticipata di 1 giorno

(SOLO IN MODALITÀ AVANZATA)
se scegli Raw, puoi inserire diverse opzioni in base al suffisso:
- d: numero di giorni dopo oggi (es.: 1d significa domani)
- y: numero di anni dopo oggi (es.: 1y significa tra un anno)

Nota: la data e l'ora sono espresse nell'ora locale del tuo computer (non in ora UTC).
" @@ -1738,28 +1733,28 @@ msgstr "locktime" msgid "heirs" msgstr "eredi" -#: bal/gui/qt/window.py:720 +#: bal/gui/qt/window.py:722 msgid "Heirs are not defined" msgstr "Gli eredi non sono definiti" -#: bal/gui/qt/window.py:838 +#: bal/gui/qt/window.py:841 #, python-brace-format msgid "In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.{}" msgstr "Nel processo di eredità il portafogli viene sempre svuotato del tutto. Le tue impostazioni richiedono una correzione degli importi.{}" -#: bal/gui/qt/window.py:856 +#: bal/gui/qt/window.py:859 msgid "CheckAlive is in the past please update it to a date in the future but less than locktime" msgstr "La data della verifica se sei vivo è nel passato: aggiornala a una data futura ma precedente al locktime" -#: bal/gui/qt/window.py:862 +#: bal/gui/qt/window.py:864 msgid "locktime is lower than threshold" msgstr "il locktime è precedente alla data della verifica se sei vivo" -#: bal/gui/qt/window.py:876 +#: bal/gui/qt/window.py:878 msgid " no backup transaction or willexecutor selected" msgstr " nessuna transazione senza Will-Executor attiva o nessun Will-Executor selezionato" -#: bal/gui/qt/window.py:915 +#: bal/gui/qt/window.py:917 msgid "" "This inheritance was already signed/sent to will-executors and you are postponing it.\n" "\n" @@ -1773,23 +1768,23 @@ msgstr "" "\n" "Firma ora la transazione di invalidazione e trasmetti il piano, poi premi di nuovo 'Prepara' per creare la nuova eredità (posticipata)." -#: bal/gui/qt/window.py:937 +#: bal/gui/qt/window.py:939 msgid "Heirs changed:" msgstr "Eredi cambiati:" -#: bal/gui/qt/window.py:939 +#: bal/gui/qt/window.py:941 msgid "Will-Executor not present:" msgstr "Will-Executor assente:" -#: bal/gui/qt/window.py:941 +#: bal/gui/qt/window.py:943 msgid "Will-Executor changed" msgstr "Will-Executor cambiato" -#: bal/gui/qt/window.py:943 +#: bal/gui/qt/window.py:945 msgid "Txfees are changed" msgstr "Le commissioni sono cambiate" -#: bal/gui/qt/window.py:950 +#: bal/gui/qt/window.py:952 msgid "" "Found CHANGES to the DATE or the HEIRS,\n" "a NEW WILL must be prepared." @@ -1797,11 +1792,11 @@ msgstr "" "Trovate MODIFICHE alla DATA o agli EREDI,\n" "bisogna preparare un NUOVO TESTAMENTO." -#: bal/gui/qt/window.py:957 +#: bal/gui/qt/window.py:959 msgid "will have to be built" msgstr "il testamento dovrà essere ricreato" -#: bal/gui/qt/window.py:977 +#: bal/gui/qt/window.py:979 #, python-brace-format msgid "" "Error:{}\n" @@ -1810,11 +1805,11 @@ msgstr "" "Errore:{}\n" " {}" -#: bal/gui/qt/window.py:979 +#: bal/gui/qt/window.py:981 msgid "Please, check your heirs, locktime and threshold!" msgstr "Controlla i tuoi eredi, il locktime e la data della verifica se sei vivo!" -#: bal/gui/qt/window.py:993 +#: bal/gui/qt/window.py:995 msgid "" "Your inheritance has been rebuilt and now needs to be signed again.\n" "\n" @@ -1826,7 +1821,7 @@ msgstr "" "Prossimo passo (manuale):\n" " 1. Premi 'Firma' per firmare la nuova transazione." -#: bal/gui/qt/window.py:1000 +#: bal/gui/qt/window.py:1002 msgid "" "Your inheritance has been rebuilt and now needs to be signed and re-sent to the will-executors.\n" "\n" @@ -1840,93 +1835,92 @@ msgstr "" " 1. Premi 'Firma' per firmare la nuova transazione.\n" " 2. Premi 'Trasmetti il piano' per inviarla ai Will-Executor." -#: bal/gui/qt/window.py:1038 +#: bal/gui/qt/window.py:1040 msgid "Electrum was unable to deserialize the transaction:" msgstr "Electrum non ha potuto deserializzare la transazione:" -#: bal/gui/qt/window.py:1052 +#: bal/gui/qt/window.py:1054 msgid "no tx" msgstr "nessuna transazione" -#: bal/gui/qt/window.py:1066 +#: bal/gui/qt/window.py:1069 msgid "Please sign and broadcast this transaction to invalidate current will" msgstr "Firma questa transazione e trasmetti il piano per invalidare il piano del testamento attuale" -#: bal/gui/qt/window.py:1074 +#: bal/gui/qt/window.py:1076 msgid "No transactions to invalidate" msgstr "Nessuna transazione da invalidare" -#: bal/gui/qt/window.py:1091 +#: bal/gui/qt/window.py:1094 msgid "Calculating Transactions" msgstr "Calcolo delle transazioni" -#: bal/gui/qt/window.py:1107 +#: bal/gui/qt/window.py:1110 #, python-brace-format msgid "signed: {}" msgstr "firmate: {}" -#: bal/gui/qt/window.py:1108 +#: bal/gui/qt/window.py:1111 #, python-brace-format msgid "signing: {}" msgstr "in firma: {}" -#: bal/gui/qt/window.py:1584 +#: bal/gui/qt/window.py:1588 msgid "Signing transactions..." msgstr "Firma delle transazioni..." -#: bal/gui/qt/window.py:1598 +#: bal/gui/qt/window.py:1602 msgid "Some transaction was not broadcasted" msgstr "Alcune transazioni non sono state trasmesse" -#: bal/gui/qt/window.py:1602 +#: bal/gui/qt/window.py:1606 msgid "All transactions are broadcasted to respective Will-Executors" msgstr "Tutte le transazioni sono state trasmesse ai rispettivi Will-Executor" -#: bal/gui/qt/window.py:1621 +#: bal/gui/qt/window.py:1625 msgid "Selecting Will-Executors" msgstr "Selezione dei Will-Executor" -#: bal/gui/qt/window.py:1636 +#: bal/gui/qt/window.py:1640 msgid "Broadcasting Transactions to Will-Executors:" msgstr "Trasmissione del piano ai Will-Executor:" -#: bal/gui/qt/window.py:1643 bal/gui/qt/window.py:2319 +#: bal/gui/qt/window.py:1647 bal/gui/qt/window.py:2323 msgid "waiting..." msgstr "in attesa..." -#: bal/gui/qt/window.py:1659 +#: bal/gui/qt/window.py:1663 msgid "checking..." msgstr "controllo in corso..." -#: bal/gui/qt/window.py:1690 +#: bal/gui/qt/window.py:1694 #, python-brace-format msgid "checking {} - {} : {}" msgstr "controllo {} - {} : {}" -#: bal/gui/qt/window.py:1699 +#: bal/gui/qt/window.py:1703 #, python-brace-format msgid "checked {} - {} : {}" msgstr "controllato {} - {} : {}" -#: bal/gui/qt/window.py:1895 bal/gui/qt/window.py:1933 -#: bal/gui/qt/window.py:1957 +#: bal/gui/qt/window.py:1899 bal/gui/qt/window.py:1937 bal/gui/qt/window.py:1961 #, python-brace-format msgid "Invalid will file: {}" msgstr "File del testamento non valido: {}" -#: bal/gui/qt/window.py:1914 bal/gui/qt/window.py:1965 +#: bal/gui/qt/window.py:1918 bal/gui/qt/window.py:1969 msgid "will" msgstr "testamento" -#: bal/gui/qt/window.py:2010 +#: bal/gui/qt/window.py:2014 msgid "Checking transactions" msgstr "Controllo delle transazioni" -#: bal/gui/qt/window.py:2059 +#: bal/gui/qt/window.py:2063 msgid "Check Transaction" msgstr "Controlla la transazione" -#: bal/gui/qt/window.py:2168 +#: bal/gui/qt/window.py:2172 msgid "" "Could not download the will-executors list.\n" "\n" @@ -1936,7 +1930,7 @@ msgstr "" "\n" "Di solito la causa è la connessione a internet o un firewall, non il plugin. Controlla la connessione (spesso una VPN aiuta) e riprova." -#: bal/gui/qt/window.py:2178 +#: bal/gui/qt/window.py:2182 msgid "" "Could not download the will-executors list over Tor.\n" "\n" @@ -1946,11 +1940,11 @@ msgstr "" "\n" "Electrum è connesso tramite Tor e la connessione sta impiegando troppo tempo. La tua connessione Tor potrebbe essere lenta. Riprova, oppure usa una VPN (o disattiva Tor per un momento) per una connessione più veloce." -#: bal/gui/qt/window.py:2189 +#: bal/gui/qt/window.py:2193 msgid "Downloading will-executors list..." msgstr "Download dell'elenco dei Will-Executor..." -#: bal/gui/qt/window.py:2250 +#: bal/gui/qt/window.py:2254 #, python-brace-format msgid "" "No active will-executor servers found for the {chain} network.\n" @@ -1961,7 +1955,7 @@ msgstr "" "\n" "Il server welist {url} ha risposto, ma non ha restituito Will-Executor per questa rete." -#: bal/gui/qt/window.py:2258 +#: bal/gui/qt/window.py:2262 #, python-brace-format msgid "" "Could not reach the configured welist server.\n" @@ -1978,16 +1972,16 @@ msgstr "" "\n" "Controlla l'URL del server welist nelle impostazioni del plugin." -#: bal/gui/qt/window.py:2268 +#: bal/gui/qt/window.py:2273 #, python-brace-format msgid "No active will-executor found for the {chain} network." msgstr "Nessun Will-Executor attivo trovato per la rete {chain}." -#: bal/gui/qt/window.py:2305 +#: bal/gui/qt/window.py:2309 msgid "Ping Will-Executors:" msgstr "Ping ai Will-Executor:" -#: bal/gui/qt/window.py:2365 +#: bal/gui/qt/window.py:2369 msgid "Ping Will-Executors" msgstr "Ping ai Will-Executor" diff --git a/tests/test_core_invalidation_fee.py b/tests/test_core_invalidation_fee.py new file mode 100644 index 0000000..cf9bd34 --- /dev/null +++ b/tests/test_core_invalidation_fee.py @@ -0,0 +1,296 @@ +""" +Tests for the fee of the on-chain INVALIDATION transaction. + +The invalidation is broadcast TODAY (it spends the will's coins so the old +pre-signed inheritance transactions become unusable), therefore it must pay the +CURRENT network fee chosen with an Electrum ``FeePolicy`` (Feerate / ETA / +Mempool; default ``"eta:2"``, i.e. confirm within 2 blocks). The will's +``baltx_fees`` (a deliberately high rate meant for inheritance transactions +broadcast years from now) is only the FALLBACK used when no usable rate is +available. + +Covers: +* Will.get_invalidation_fee_rate() - rate selection and fallback rules +* Will.invalidate_will() - the chosen rate is really used for the fee + +Run (standalone or with pytest): + QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src \ + python3 tests/test_core_invalidation_fee.py + QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src \ + python3 -m pytest tests/test_core_invalidation_fee.py -q +""" + +import os +import sys +from unittest.mock import MagicMock, patch + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) + +from electrum.fee_policy import FeePolicy, FeeTimeEstimates +from electrum.transaction import Transaction + +from bal.core.will import ( + INVALIDATION_FEE_ETA_BLOCKS, + INVALIDATION_FEE_POLICY_DEFAULT, + Will, + WillItem, +) + +# WillItem parses a raw tx and calls add_info_from_wallet; no live wallet here. +_patcher = patch.object(Transaction, "add_info_from_wallet") +_patcher.start() + +# A valid serialized transaction (1 input, 1 output) reused by the core tests. +_VALID_TX_HEX = ( + "01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65b" + "f38633b424eb4031000000006c493046022100a82bbc57a0136751e543" + "3f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7d" + "e89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501" + "2102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae3" + "5cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a" + "42146f11ef8414ae929feaafc388ac00000000" +) +# The outpoint spent by _VALID_TX_HEX (input 0). +_PREVOUT_STR = "3140eb24b43386f35ba69e3875eb6c93130ac66201d01c58f598defc949a5c2a:0" +_CHANGE_ADDR = "14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs" +_BALANCE = 1000000 +# The will's baltx_fees used as the fallback rate in every test (sat/vbyte). +_BALTX_FEES = 20 + + +# ------------------------------------------------------------------ # +# Helpers +# ------------------------------------------------------------------ # + +def _wallet_with_eta_estimate(fee_per_kb): + """Wallet whose ETA fee lookup returns ``fee_per_kb`` (sat/kvbyte). + + Electrum's ``FeePolicy`` (method ``eta``) resolves the target through + ``network.fee_estimates.eta_to_fee``. ``fee_per_kb`` may also be an + Exception instance: the call then raises it (broken network object), or a + non-int-convertible object to exercise the invalid-value path. + """ + wallet = MagicMock() + wallet.get_change_addresses_for_new_transaction.return_value = [_CHANGE_ADDR] + network = MagicMock() + if isinstance(fee_per_kb, Exception): + network.fee_estimates.eta_to_fee.side_effect = fee_per_kb + else: + network.fee_estimates.eta_to_fee.return_value = fee_per_kb + wallet.network = network + return wallet + + +def _make_will(): + """One VALID will item spending _PREVOUT_STR with a known input value.""" + item = WillItem({ + "tx": _VALID_TX_HEX, + "heirs": {"alice": ["addr_alice", 5000, "30d"]}, + "willexecutor": None, + "status": "", + "description": "", + "time": 0, + "change": "", + "baltx_fees": _BALTX_FEES, + }) + item.STATUS = WillItem.copy_status_table(WillItem.STATUS_DEFAULT) + item.tx.inputs()[0]._TxInput__value_sats = _BALANCE + return {"willtxid1": item} + + +def _make_utxo(): + """Wallet-side UTXO matching the will input.""" + utxo = MagicMock() + utxo.prevout.to_str.return_value = _PREVOUT_STR + utxo.is_coinbase_output.return_value = False + utxo.block_height = 1 + utxo.value_sats.return_value = _BALANCE + return utxo + + +def _invalidation_output_value(wallet, estimated_size=200, descriptor=None): + """Run Will.invalidate_will and return the value of the final output. + + Electrum's tx building is mocked (as in test_core_will_invalidate.py): + ``estimated_size`` is the vsize the fee is computed from. + """ + wallet.get_utxos.return_value = [_make_utxo()] + mock_output = MagicMock() + mock_tx = MagicMock() + mock_tx.estimated_size.return_value = estimated_size + with patch("bal.core.will.Util.get_current_height", return_value=800000), \ + patch("electrum.transaction.PartialTxOutput.from_address_and_value", + return_value=mock_output) as mock_out, \ + patch("electrum.transaction.PartialTransaction.from_io", + return_value=mock_tx): + result = Will.invalidate_will( + _make_will(), wallet, _BALTX_FEES, + fee_policy_descriptor=descriptor, + ) + assert result is mock_tx + # 1st call: full balance (size estimation); 2nd call: balance - fee. + return mock_out.call_args_list[1][0][1] + + +# ------------------------------------------------------------------ # +# Will.get_invalidation_fee_rate - policy selection +# ------------------------------------------------------------------ # + +def test_default_policy_is_two_block_eta(): + """The owner's decision: default fee policy confirms within 2 blocks. + + Uses a REAL ``FeeTimeEstimates`` holding data only for the 2-block target, + proving the default policy really asks Electrum for target 2. + """ + assert INVALIDATION_FEE_ETA_BLOCKS == 2 + assert INVALIDATION_FEE_POLICY_DEFAULT == "eta:2" + network = MagicMock() + estimates = FeeTimeEstimates() + estimates.set_data(2, 3000) + network.fee_estimates = estimates + wallet = MagicMock() + wallet.network = network + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == (3.0, "eta") + + +def test_default_policy_descriptor_is_parsed_by_electrum_fee_policy(): + """The setting is a real Electrum FeePolicy descriptor.""" + policy = FeePolicy(INVALIDATION_FEE_POLICY_DEFAULT) + assert policy.method.name.lower() == "eta" + assert policy.value == 2 + + +def test_network_estimate_is_used(): + """A live estimate (sat/kvbyte) becomes the rate in sat/vbyte.""" + wallet = _wallet_with_eta_estimate(3000) + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == (3.0, "eta") + + +def test_network_estimate_higher_than_baltx_fees_is_used_as_is(): + """No cap: if the market is above baltx_fees, the market rate is used.""" + wallet = _wallet_with_eta_estimate(45000) + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == (45.0, "eta") + + +def test_fractional_estimate_is_kept(): + """Electrum estimates may be fractional (e.g. 1.5 sat/vbyte).""" + wallet = _wallet_with_eta_estimate(1500) + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == (1.5, "eta") + + +def test_fixed_feerate_policy_works_offline(): + """A fixed 'feerate' policy needs no network at all.""" + wallet = MagicMock() + wallet.network = None + assert Will.get_invalidation_fee_rate( + wallet, _BALTX_FEES, "feerate:5000" + ) == (5.0, "feerate") + + +def test_mempool_policy_uses_depth_estimate(): + """A 'mempool' policy asks Electrum for a depth-based estimate.""" + wallet = MagicMock() + network = MagicMock() + network.mempool_fees.depth_to_fee.return_value = 3000 + wallet.network = network + assert Will.get_invalidation_fee_rate( + wallet, _BALTX_FEES, "mempool:1000000" + ) == (3.0, "mempool") + + +def test_offline_wallet_falls_back_to_baltx_fees(): + """No network object (offline) with a dynamic policy: baltx_fees.""" + wallet = MagicMock() + wallet.network = None + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == ( + _BALTX_FEES, + "fallback", + ) + + +def test_missing_estimate_falls_back(): + """Estimates not received yet: Electrum returns None.""" + wallet = _wallet_with_eta_estimate(None) + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == ( + _BALTX_FEES, + "fallback", + ) + + +def test_estimate_error_falls_back_and_does_not_raise(): + """A broken network object must never break the invalidation.""" + wallet = _wallet_with_eta_estimate(RuntimeError("server gone")) + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == ( + _BALTX_FEES, + "fallback", + ) + + +def test_non_positive_or_unconvertible_estimates_fall_back(): + """Only a positive integer rate is trusted.""" + for bad in (0, -1000, object()): + wallet = _wallet_with_eta_estimate(bad) + assert Will.get_invalidation_fee_rate(wallet, _BALTX_FEES) == ( + _BALTX_FEES, + "fallback", + ), bad + + +def test_invalid_policy_descriptor_uses_the_default(): + """A corrupt stored descriptor must not break anything: default is used.""" + wallet = _wallet_with_eta_estimate(3000) + assert Will.get_invalidation_fee_rate( + wallet, _BALTX_FEES, "not-a-policy" + ) == (3.0, "eta") + + +def test_no_wallet_falls_back(): + assert Will.get_invalidation_fee_rate(None, _BALTX_FEES) == ( + _BALTX_FEES, + "fallback", + ) + + +# ------------------------------------------------------------------ # +# Will.invalidate_will uses the chosen rate +# ------------------------------------------------------------------ # + +def test_invalidation_pays_network_fee_not_baltx_fees(): + """3 sat/vbyte on 200 vbytes = 600 sat (not 200 * 20 = 4000 sat).""" + wallet = _wallet_with_eta_estimate(3000) + assert _invalidation_output_value(wallet) == _BALANCE - 600 + + +def test_invalidation_offline_pays_baltx_fees(): + """Offline: unchanged behaviour, 200 * 20 = 4000 sat.""" + wallet = _wallet_with_eta_estimate(None) + assert _invalidation_output_value(wallet) == _BALANCE - 4000 + + +def test_invalidation_uses_fixed_feerate_policy_offline(): + """A fixed feerate pays exactly that rate even without a network.""" + wallet = MagicMock() + wallet.network = None + assert _invalidation_output_value( + wallet, descriptor="feerate:5000" + ) == _BALANCE - 1000 # 200 vbytes * 5 sat/vbyte + + +def test_fractional_fee_is_rounded_up_to_whole_satoshis(): + """1.5 sat/vbyte on 201 vbytes = 301.5 -> 302 sat (never below the rate).""" + wallet = _wallet_with_eta_estimate(1500) + value = _invalidation_output_value(wallet, estimated_size=201) + assert value == _BALANCE - 302 + assert isinstance(value, int) + + +# ------------------------------------------------------------------ # +# Main +# ------------------------------------------------------------------ # + +if __name__ == "__main__": + for name in sorted(dir()): + if name.startswith("test_"): + globals()[name]() + print(f" [OK] {name}") + print("[OK] All invalidation fee tests passed") diff --git a/tests/test_core_will_invalidate.py b/tests/test_core_will_invalidate.py index 6a93cab..f5bbd2d 100644 --- a/tests/test_core_will_invalidate.py +++ b/tests/test_core_will_invalidate.py @@ -47,6 +47,24 @@ _VALID_TX_HEX = ( # The prevout string that _VALID_TX_HEX spends (input 0). _PREVOUT_STR = "3140eb24b43386f35ba69e3875eb6c93130ac66201d01c58f598defc949a5c2a:0" +# A second transaction identical to _VALID_TX_HEX but spending a DIFFERENT +# prevout. It stands for a superseded (REPLACED) will transaction that commits +# different coins than the current valid one. +_REPLACED_TX_HEX = "0100000001ff" + _VALID_TX_HEX[len("01000000012a"):] +_REPLACED_PREVOUT_STR = _PREVOUT_STR[:-4] + "ff:0" + +# A 2-input transaction: version 2 with two parallel inputs. Built from +# _VALID_TX_HEX by duplicating its (298-hex) input with a DIFFERENT prevout +# (first byte "ff", so the second input spends _REPLACED_PREVOUT_STR) and +# keeping the unchanged outputs. +_INPUT_LEN = 298 +_TWO_INPUT_TX_HEX = ( + "0100000002" + + _VALID_TX_HEX[10:10 + _INPUT_LEN] + + "ff" + _VALID_TX_HEX[10 + 2:10 + _INPUT_LEN] + + _VALID_TX_HEX[10 + _INPUT_LEN:] +) + # Change address for the invalidation output. _CHANGE_ADDR = "14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs" @@ -55,8 +73,9 @@ _CHANGE_ADDR = "14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs" # Helpers # ------------------------------------------------------------------ # -def _make_willitem(value_sats=1000000, valid=True, extra_heirs=None): - """Create a WillItem from _VALID_TX_HEX with a known input value. +def _make_willitem(value_sats=1000000, valid=True, extra_heirs=None, + tx_hex=_VALID_TX_HEX): + """Create a WillItem from ``tx_hex`` with a known input value. The input's ``_trusted_value_sats`` is set so that ``invalidate_will`` can read the balance from it. @@ -65,7 +84,7 @@ def _make_willitem(value_sats=1000000, valid=True, extra_heirs=None): if extra_heirs: heirs.update(extra_heirs) item = WillItem({ - "tx": _VALID_TX_HEX, + "tx": tx_hex, "heirs": heirs, "willexecutor": None, "status": "", @@ -93,16 +112,24 @@ def _make_utxo(prevout_str=None, value_sats=1000000, is_coinbase=False): utxo.prevout.to_str.return_value = prevout_str utxo.is_coinbase_output.return_value = is_coinbase utxo.block_height = 1 + utxo.block_txpos = 0 utxo.value_sats.return_value = value_sats return utxo def _mock_wallet(utxos, change_addr=_CHANGE_ADDR): - """Create a mock wallet with the given UTXOs and change address.""" + """Create a mock wallet with the given UTXOs and change address. + + ``network`` is left ``None`` so ``Will.invalidate_will`` uses the + ``fees_per_byte`` FALLBACK instead of Electrum's live estimate: these tests + exercise the pure fee arithmetic (``fee = size * fees_per_byte``). The + policy-based rate selection has its own tests in + ``tests/test_core_invalidation_fee.py``. + """ wallet = MagicMock() wallet.get_utxos.return_value = utxos wallet.get_change_addresses_for_new_transaction.return_value = [change_addr] - wallet.network = MagicMock() + wallet.network = None return wallet @@ -292,6 +319,77 @@ class TestInvalidateWill: second_call_value = mock_out.call_args_list[1][0][1] assert second_call_value == 998000 + def test_replaced_item_inputs_are_invalidated(self): + """A REPLACED (superseded) item's funding UTXO is also spent. + + Regression: a will rebuilt after an heir change supersedes the old + signed transaction, which is kept but marked REPLACED (VALID cleared). + The old transaction may have been pushed to the will-executors and may + spend DIFFERENT coins than the current valid one, so its UTXO must be + included in the invalidation. Otherwise it would stay unspent and a + will-executor could still broadcast the superseded transaction. + """ + valid_item = _make_willitem(value_sats=1000000) + replaced_item = _make_willitem( + value_sats=2000000, tx_hex=_REPLACED_TX_HEX + ) + replaced_item.set_status("REPLACED", True) + assert replaced_item.get_status("VALID") is False + + will = {"valid": valid_item, "replaced": replaced_item} + wallet = _mock_wallet([ + _make_utxo(), + _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=2000000), + ]) + + result, mock_from_io, _ = _run_invalidate(will, wallet) + + assert result is not None + spent = [u.prevout.to_str() for u in mock_from_io.call_args[0][0]] + assert _PREVOUT_STR in spent + assert _REPLACED_PREVOUT_STR in spent + + def test_replaced_item_contributes_balance(self): + """The balance of a REPLACED item is included in the invalidation.""" + valid_item = _make_willitem(value_sats=1000000) + replaced_item = _make_willitem( + value_sats=2000000, tx_hex=_REPLACED_TX_HEX + ) + replaced_item.set_status("REPLACED", True) + + will = {"valid": valid_item, "replaced": replaced_item} + wallet = _mock_wallet([ + _make_utxo(), + _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=2000000), + ]) + + _, _, mock_out = _run_invalidate(will, wallet, fees_per_byte=10) + + # Balance = 1000000 + 2000000 = 3000000, fee = 200 * 10 = 2000 + second_call_value = mock_out.call_args_list[1][0][1] + assert second_call_value == 2998000 + + def test_invalidated_item_inputs_are_not_invalidated(self): + """A purely INVALIDATED item (already neutralised) is still excluded.""" + valid_item = _make_willitem(value_sats=1000000) + invalid_item = _make_willitem( + value_sats=2000000, tx_hex=_REPLACED_TX_HEX, valid=False + ) + assert invalid_item.get_status("INVALIDATED") is True + + will = {"valid": valid_item, "invalid": invalid_item} + wallet = _mock_wallet([ + _make_utxo(), + _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=2000000), + ]) + + result, mock_from_io, _ = _run_invalidate(will, wallet) + + assert result is not None + spent = [u.prevout.to_str() for u in mock_from_io.call_args[0][0]] + assert _PREVOUT_STR in spent + assert _REPLACED_PREVOUT_STR not in spent + def test_first_from_io_uses_full_balance(self): """The first from_io call uses the full balance (before fee deduction) to estimate the fee.""" @@ -353,6 +451,152 @@ class TestInvalidateWill: second_call_value = mock_out.call_args_list[1][0][1] assert second_call_value == 1000000 + def test_invalidation_spends_oldest_input_per_tx(self): + """For each will tx, only its OLDEST funding coin is spent. + + A will item whose transaction has two inputs (old and new coin) must + spend only the coin that has been in the wallet longest, leaving the + newer coin unspent for the rebuilt (postponed) will. + """ + item = _make_willitem(value_sats=1000000, tx_hex=_TWO_INPUT_TX_HEX) + will = {"willtxid1": item} + old_utxo = _make_utxo(prevout_str=_PREVOUT_STR, value_sats=1000000) + old_utxo.block_height = 1 + new_utxo = _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=2000000) + new_utxo.block_height = 500 # newer than the old coin + wallet = _mock_wallet([old_utxo, new_utxo]) + + result, mock_from_io, _ = _run_invalidate(will, wallet) + + assert result is not None + spent = [u.prevout.to_str() for u in mock_from_io.call_args[0][0]] + assert spent == [_PREVOUT_STR] + + def test_invalidation_fallback_appends_next_oldest(self): + """If the oldest coin cannot cover the fee, the next-oldest is appended. + + The invalidation must not be dropped just because the single oldest + coin is small: it keeps adding will coins, oldest first, until the + balance covers the estimated fee. + """ + item = _make_willitem(value_sats=1000000, tx_hex=_TWO_INPUT_TX_HEX) + will = {"willtxid1": item} + old_utxo = _make_utxo(prevout_str=_PREVOUT_STR, value_sats=1000) + old_utxo.block_height = 1 + new_utxo = _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=1000000) + new_utxo.block_height = 500 + wallet = _mock_wallet([old_utxo, new_utxo]) + + # estimated_size (200) * fees_per_byte (10) = 2000 > balance (1000) + result, mock_from_io, mock_out = _run_invalidate(will, wallet, fees_per_byte=10) + + assert result is not None + spent = [u.prevout.to_str() for u in mock_from_io.call_args[0][0]] + assert _PREVOUT_STR in spent + assert _REPLACED_PREVOUT_STR in spent + # balance = 1000 + 1000000, fee = 2000 + final_value = mock_out.call_args[0][1] + assert final_value == 999000 + + def test_invalidation_treats_unconfirmed_as_newest(self): + """An unconfirmed (unknown-height) coin is never chosen as the oldest.""" + item = _make_willitem(value_sats=1000000, tx_hex=_TWO_INPUT_TX_HEX) + will = {"willtxid1": item} + confirmed = _make_utxo(prevout_str=_PREVOUT_STR, value_sats=1000000) + confirmed.block_height = 10 + unconfirmed = _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=2000000) + unconfirmed.block_height = None + wallet = _mock_wallet([unconfirmed, confirmed]) + + result, mock_from_io, _ = _run_invalidate(will, wallet) + + assert result is not None + spent = [u.prevout.to_str() for u in mock_from_io.call_args[0][0]] + assert spent == [_PREVOUT_STR] + + def test_invalidation_same_height_order_is_deterministic(self): + """Coins at the same height are ordered by block position, not list order.""" + item = _make_willitem(value_sats=1000000, tx_hex=_TWO_INPUT_TX_HEX) + will = {"willtxid1": item} + later = _make_utxo(prevout_str=_PREVOUT_STR, value_sats=1000000) + later.block_height = 10 + later.block_txpos = 5 + earlier = _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=2000000) + earlier.block_height = 10 + earlier.block_txpos = 1 + wallet = _mock_wallet([later, earlier]) + + result, mock_from_io, _ = _run_invalidate(will, wallet) + + assert result is not None + spent = [u.prevout.to_str() for u in mock_from_io.call_args[0][0]] + assert spent == [_REPLACED_PREVOUT_STR] # block_txpos 1 < 5 => older + + def test_sub_dust_change_returns_none(self): + """A change output below the dust limit is never created. + + Regression: previously any positive change (balance - fee > 0) was + accepted, which could produce a non-standard (unrelayable) output and + leave the will un-invalidated. + """ + item = _make_willitem(value_sats=2200) + will = {"willtxid1": item} + wallet = _mock_wallet([_make_utxo(value_sats=2200)]) + wallet.dust_threshold.return_value = 546 + + # fee = estimated_size (200) * fees_per_byte (10) = 2000; + # balance - fee = 200 < dust (546) -> no transaction. + result, _, _ = _run_invalidate(will, wallet, fees_per_byte=10) + + assert result is None + + def test_change_equal_to_dust_is_allowed(self): + """Change exactly at the dust threshold is accepted (>= comparison).""" + item = _make_willitem(value_sats=2546) + will = {"willtxid1": item} + wallet = _mock_wallet([_make_utxo(value_sats=2546)]) + wallet.dust_threshold.return_value = 546 + + # balance - fee = 2546 - 2000 = 546 == dust -> transaction. + result, _, mock_out = _run_invalidate(will, wallet, fees_per_byte=10) + + assert result is not None + assert mock_out.call_args_list[1][0][1] == 546 + + def test_fallback_stops_when_change_still_dust(self): + """Coins are appended until the change clears dust; else returns None.""" + item = _make_willitem(value_sats=1000000, tx_hex=_TWO_INPUT_TX_HEX) + will = {"willtxid1": item} + old_utxo = _make_utxo(prevout_str=_PREVOUT_STR, value_sats=1000) + old_utxo.block_height = 1 + new_utxo = _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=1200) + new_utxo.block_height = 500 + wallet = _mock_wallet([old_utxo, new_utxo]) + wallet.dust_threshold.return_value = 546 + + # fee = 2000; total balance = 1000 + 1200 = 2200; + # change = 200 < dust (546) and no coin is left -> None. + result, _, _ = _run_invalidate(will, wallet, fees_per_byte=10) + + assert result is None + + def test_fallback_succeeds_once_change_clears_dust(self): + """Appending the next coin yields a change above dust -> transaction.""" + item = _make_willitem(value_sats=1000000, tx_hex=_TWO_INPUT_TX_HEX) + will = {"willtxid1": item} + old_utxo = _make_utxo(prevout_str=_PREVOUT_STR, value_sats=1000) + old_utxo.block_height = 1 + new_utxo = _make_utxo(prevout_str=_REPLACED_PREVOUT_STR, value_sats=2546) + new_utxo.block_height = 500 + wallet = _mock_wallet([old_utxo, new_utxo]) + wallet.dust_threshold.return_value = 546 + + # fee = 2000; total balance = 3546; change = 1546 >= dust -> transaction. + result, _, mock_out = _run_invalidate(will, wallet, fees_per_byte=10) + + assert result is not None + assert mock_out.call_args_list[-1][0][1] == 1546 + # ================================================================== # # Will.set_invalidate - status flag cascade diff --git a/tests/test_core_willtree_father.py b/tests/test_core_willtree_father.py new file mode 100644 index 0000000..e3fcad1 --- /dev/null +++ b/tests/test_core_willtree_father.py @@ -0,0 +1,145 @@ +""" +Tests for the parent/child tree of the will transactions (``father`` links). + +Bug reproduced from a real wallet (Electrum log, Check wizard): + + File "bal/core/will.py", in check_invalidated + or willtree[w.father].get_status("CONFIRMED") + KeyError: 'a65a682f…' + +``father`` is persisted in the wallet (``"Father"``) and was never cleared, so +after the father tx was removed from the will the child pointed to a txid that +no longer exists. ``Will.add_willtree`` now rebuilds the links from scratch and +``Will.check_invalidated`` treats a missing father as "no father". + +Run (standalone or with pytest): + QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src \ + python3 tests/test_core_willtree_father.py +""" + +import os +import sys +from unittest.mock import MagicMock, patch + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) + +from electrum.transaction import Transaction + +from bal.core.will import Will, WillItem + +# WillItem parses a raw tx and calls add_info_from_wallet; no live wallet here. +_patcher = patch.object(Transaction, "add_info_from_wallet") +_patcher.start() + +# A valid serialized transaction (1 input, 1 output) reused by the core tests. +_VALID_TX_HEX = ( + "01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65b" + "f38633b424eb4031000000006c493046022100a82bbc57a0136751e543" + "3f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7d" + "e89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501" + "2102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae3" + "5cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a" + "42146f11ef8414ae929feaafc388ac00000000" +) +# The input of _VALID_TX_HEX spends this outpoint (little-endian in the hex). +_FUNDING_ID = "3140eb24b43386f35ba69e3875eb6c93130ac66201d01c58f598defc949a5c2a" +# PARENT: _VALID_TX_HEX itself. CHILD: the same tx with its input re-pointed to +# the parent's txid, so the child spends the parent's output 0. +_PARENT_ID = Transaction(_VALID_TX_HEX).txid() +_CHILD_TX_HEX = _VALID_TX_HEX.replace( + bytes.fromhex(_FUNDING_ID)[::-1].hex(), bytes.fromhex(_PARENT_ID)[::-1].hex() +) +# A txid that is not in the will (the "removed father" of the bug report). +_MISSING_ID = "a65a682f2975f0dffd9eccce9392833f227badf617fc67815674736d594d4115" + + +def _item(tx_hex=_VALID_TX_HEX, father=None): + """WillItem built from ``tx_hex``, optionally with a stored father.""" + data = {"tx": tx_hex, "heirs": {}, "willexecutor": None} + if father is not None: + data["Father"] = father + return WillItem(data) + + +def _wallet_without_adb(): + """Wallet whose check_invalidated path skips on-chain lookups.""" + wallet = MagicMock() + wallet.adb = None + return wallet + + +def test_stale_father_is_discarded_by_add_willtree(): + child = _item(father=_MISSING_ID) + will = {child._id: child} + Will.add_willtree(will) + assert child.father is None + + +def test_check_invalidated_does_not_crash_on_stale_father(): + """The exact failure of the bug report, now without KeyError.""" + child = _item(father=_MISSING_ID) + will = {child._id: child} + Will.add_willtree(will) + Will.check_invalidated(will, [], _wallet_without_adb()) + + +def test_check_invalidated_tolerates_missing_father_without_add_willtree(): + """Defensive path: even if the tree was not rebuilt, no KeyError.""" + child = _item(father=_MISSING_ID) + will = {child._id: child} + Will.check_invalidated(will, [], _wallet_without_adb()) + assert child.father == _MISSING_ID + + +def test_real_parent_child_link_is_built(): + parent = _item() + child = _item(_CHILD_TX_HEX) + will = {_PARENT_ID: parent, child._id: child} + Will.add_willtree(will) + assert child.father == _PARENT_ID + assert [c[0] for c in parent.children] == [child._id] + assert parent.father is None + + +def test_stale_father_is_replaced_by_the_real_parent(): + parent = _item() + child = _item(_CHILD_TX_HEX, father=_MISSING_ID) + will = {_PARENT_ID: parent, child._id: child} + Will.add_willtree(will) + assert child.father == _PARENT_ID + + +def test_child_of_unconfirmed_parent_is_not_checked_against_utxos(): + """Existing rule kept: a child of a VALID (not mined) parent is skipped.""" + parent = _item() + child = _item(_CHILD_TX_HEX) + will = {_PARENT_ID: parent, child._id: child} + Will.add_willtree(will) + wallet = MagicMock() + # Funding not spent on-chain: nothing gets invalidated, we only observe + # which items had their inputs checked. + with patch.object(Will, "check_tx_height", return_value=-2) as height, \ + patch.object(Will, "_funding_really_spent", return_value=False): + Will.check_invalidated({child._id: child, _PARENT_ID: parent}, [], wallet) + # Only the parent (a root) looked up its height; the child was skipped. + checked = [call.args[0] for call in height.call_args_list] + assert checked == [parent.tx] + + +def test_set_invalidate_cascades_to_children_built_by_add_willtree(): + """add_willtree builds children as a LIST: invalidation must cascade.""" + parent = _item() + child = _item(_CHILD_TX_HEX) + will = {parent._id: parent, child._id: child} + Will.add_willtree(will) + Will.set_invalidate(parent._id, will) + assert parent.get_status("INVALIDATED") is True + assert child.get_status("INVALIDATED") is True + + +if __name__ == "__main__": + for name in sorted(dir()): + if name.startswith("test_"): + globals()[name]() + print(f" [OK] {name}") + print("[OK] All will-tree father tests passed") diff --git a/tests/test_settings_invalidation_fee.py b/tests/test_settings_invalidation_fee.py new file mode 100644 index 0000000..0b1a259 --- /dev/null +++ b/tests/test_settings_invalidation_fee.py @@ -0,0 +1,208 @@ +""" +Tests for the "Invalidation fee policy" settings-dialog row (ADVANCED only). + +Covers: + - The row is hidden in BASIC mode and visible in ADVANCED mode (like the + other advanced-only rows). + - The Electrum fee widget (``FeeSlider`` + ``FeeComboBox``) is bound to the + ``INVALIDATION_FEE_POLICY`` config: choosing a Feerate / ETA / Mempool + policy persists its descriptor (``":"``). + - "Reset to Default Setting" restores the policy to its default ("eta:2"). + +Run: + source "$BAL_HOME/electrum/env/bin/activate" + QT_QPA_PLATFORM=offscreen python3 tests/test_settings_invalidation_fee.py +""" + +import sys +import tempfile + +sys.path.insert(0, __file__.rsplit("/", 2)[0]) + +from unittest.mock import patch + +from PyQt6.QtWidgets import QApplication, QComboBox, QLabel, QPushButton + +_app = QApplication.instance() or QApplication(sys.argv) + +from electrum.fee_policy import FeeMethod, FeePolicy +from electrum.gui.qt.fee_slider import FeeComboBox, FeeSlider +from electrum.simple_config import SimpleConfig + +import bal.gui.qt.plugin as plugin_mod +from bal.gui.qt.plugin import Plugin + +LABEL_TEXT = "Invalidation fee policy" + + +def _isolated_config(**overrides): + """An in-memory SimpleConfig that never touches the real Electrum config.""" + opts = {"electrum_path": tempfile.mkdtemp(prefix="bal_test_")} + opts.update(overrides) + return SimpleConfig(opts) + + +def _build_dialog(user_type, config=None, **config_overrides): + """Build the plugin settings dialog for *user_type* and return it. + + *config* (optional) lets a test pass a pre-populated SimpleConfig; overrides + are used to build one otherwise. Note that overrides become command-line + options, which ``set()`` refuses to modify - use *config* when the test needs + a mutable stored value. + """ + cfg = config if config is not None else _isolated_config(**config_overrides) + plugin = Plugin(None, cfg, "bal") + plugin.get_window_title = lambda s: s + plugin.read_file = lambda *a: b"" + plugin.broadcast_transactions = lambda *a, **k: None + plugin.update_all = lambda *a, **k: None + plugin.USER_TYPE.set(user_type) + captured = {} + with patch.object( + plugin_mod, "show_modal", lambda d: captured.setdefault("d", d) or True + ): + plugin.settings_dialog(None) + return plugin, captured["d"] + + +def _find_label(dialog): + """Return the "Invalidation fee policy" QLabel (wrapped by _hide_if_basic).""" + for lbl in dialog.findChildren(QLabel): + if lbl.text() == LABEL_TEXT: + return lbl + raise AssertionError("invalidation fee policy label not found") + + +# ------------------------------------------------------------------ # +# Visibility +# ------------------------------------------------------------------ # + +def test_row_hidden_in_basic_and_visible_in_advanced(): + _plugin, basic = _build_dialog("basic") + assert _find_label(basic).isHidden() is True + basic.close() + + _plugin, advanced = _build_dialog("advanced") + assert _find_label(advanced).isHidden() is False + advanced.close() + + +def _user_type_combo(dialog): + """Return the BASIC/ADVANCED selector combo of the settings dialog.""" + for combo in dialog.findChildren(QComboBox): + if combo.itemText(0) == "BASIC" and combo.itemText(1) == "ADVANCED": + return combo + raise AssertionError("user type combo not found") + + +def test_row_follows_live_mode_switch(): + """The row must show/hide when the mode is toggled at runtime (not only + the initial creation-time visibility).""" + # BASIC -> ADVANCED (with the confirmation phrase). + plugin, dialog = _build_dialog("basic") + combo = _user_type_combo(dialog) + with patch( + "PyQt6.QtWidgets.QInputDialog.getText", + return_value=("at My Risk", True), + ): + combo.setCurrentIndex(1) + assert plugin.USER_TYPE.get() == "advanced" + assert _find_label(dialog).isHidden() is False + dialog.close() + + # ADVANCED -> BASIC (no confirmation needed). + plugin, dialog = _build_dialog("advanced") + combo = _user_type_combo(dialog) + combo.setCurrentIndex(0) + assert plugin.USER_TYPE.get() == "basic" + assert _find_label(dialog).isHidden() is True + dialog.close() + + +# ------------------------------------------------------------------ # +# Persistence +# ------------------------------------------------------------------ # + +def test_default_policy_is_eta_two(): + plugin, dialog = _build_dialog("advanced") + assert plugin.INVALIDATION_FEE_POLICY.default == "eta:2" + assert plugin.INVALIDATION_FEE_POLICY.get() == "eta:2" + dialog.close() + + +def test_hand_edited_policy_is_not_rewritten_on_open(): + """Opening the dialog must not normalize (snap/persist) a hand-edited + descriptor; only an explicit user change should do that.""" + # "eta:3" is not a slider target (FEE_ETA_TARGETS = [1008, 144, 25, 10, + # 5, 2, 1]), so a naive slider.update() would snap it to "eta:2". The value + # is written to the isolated config (not passed as an override, which would + # become a non-modifiable command-line option and hide the bug). + config = _isolated_config() + config.set_key("bal_invalidation_fee_policy", "eta:3") + plugin, dialog = _build_dialog("advanced", config=config) + assert plugin.INVALIDATION_FEE_POLICY.get() == "eta:3" + dialog.close() + + +def test_choosing_a_fixed_feerate_is_persisted(): + plugin, dialog = _build_dialog("advanced") + combo = dialog.findChildren(FeeComboBox)[0] + slider = dialog.findChildren(FeeSlider)[0] + + combo.setCurrentIndex(FeeMethod.slider_index_of_method(FeeMethod.FEERATE)) + slider.fee_policy.value = 5000 # sat/kvB + slider.update() + + assert plugin.INVALIDATION_FEE_POLICY.get() == "feerate:5000" + dialog.close() + + +def test_choosing_an_eta_target_is_persisted(): + plugin, dialog = _build_dialog("advanced") + combo = dialog.findChildren(FeeComboBox)[0] + slider = dialog.findChildren(FeeSlider)[0] + + combo.setCurrentIndex(FeeMethod.slider_index_of_method(FeeMethod.ETA)) + slider.fee_policy.value = 10 # blocks (valid FixedTargets entry) + slider.update() + + assert plugin.INVALIDATION_FEE_POLICY.get() == "eta:10" + dialog.close() + + +# ------------------------------------------------------------------ # +# Reset to default +# ------------------------------------------------------------------ # + +def test_reset_to_default_restores_eta_two(): + plugin, dialog = _build_dialog("advanced") + combo = dialog.findChildren(FeeComboBox)[0] + slider = dialog.findChildren(FeeSlider)[0] + + combo.setCurrentIndex(FeeMethod.slider_index_of_method(FeeMethod.FEERATE)) + slider.fee_policy.value = 5000 + slider.update() + assert plugin.INVALIDATION_FEE_POLICY.get() == "feerate:5000" + + reset_btn = [ + b for b in dialog.findChildren(QPushButton) + if b.text() == "Reset to Default Setting" + ][0] + reset_btn.click() + + assert plugin.INVALIDATION_FEE_POLICY.get() == "eta:2" + assert slider.fee_policy.get_descriptor() == "eta:2" + assert FeePolicy("eta:2").method == FeeMethod.ETA + dialog.close() + + +# ------------------------------------------------------------------ # +# Main +# ------------------------------------------------------------------ # + +if __name__ == "__main__": + for name in sorted(dir()): + if name.startswith("test_"): + globals()[name]() + print(f" [OK] {name}") + print("[OK] All invalidation fee settings tests passed")