diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index a6f027da4..5073941e6 100644
--- a/electrum/gui/qt/main_window.py
+++ b/electrum/gui/qt/main_window.py
@@ -1118,8 +1118,17 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
*,
external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: PaymentIdentifier = None,
+ show_sign_button: bool = True,
+ show_broadcast_button: bool = True,
):
- show_transaction(tx, parent=self, external_keypairs=external_keypairs, payment_identifier=payment_identifier)
+ show_transaction(
+ tx,
+ parent=self,
+ external_keypairs=external_keypairs,
+ payment_identifier=payment_identifier,
+ show_sign_button=show_sign_button,
+ show_broadcast_button=show_broadcast_button,
+ )
def show_lightning_transaction(self, tx_item):
from .lightning_tx_dialog import LightningTxDialog
diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py
index 5914acea0..74c50760d 100644
--- a/electrum/gui/qt/transaction_dialog.py
+++ b/electrum/gui/qt/transaction_dialog.py
@@ -411,6 +411,8 @@ def show_transaction(
external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: 'PaymentIdentifier' = None,
on_closed: Callable[[], None] = None,
+ show_sign_button: bool = True,
+ show_broadcast_button: bool = True,
):
try:
d = TxDialog(
@@ -421,6 +423,10 @@ def show_transaction(
payment_identifier=payment_identifier,
on_closed=on_closed,
)
+ if not show_sign_button:
+ d.sign_button.setVisible(False)
+ if not show_broadcast_button:
+ d.broadcast_button.setVisible(False)
except SerializationError as e:
_logger.exception('unable to deserialize the transaction')
parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
diff --git a/electrum/plugins/timelock_recovery/__init__.py b/electrum/plugins/timelock_recovery/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/electrum/plugins/timelock_recovery/intro.txt b/electrum/plugins/timelock_recovery/intro.txt
new file mode 100644
index 000000000..df53aad6a
--- /dev/null
+++ b/electrum/plugins/timelock_recovery/intro.txt
@@ -0,0 +1,72 @@
+Timelock Recovery is a mechanism which, in case of a catastrophic event
+(death or loss of your master key), can send your Bitcoin to a secondary wallet of your choice
+within a time-window (i.e. 90 days).
+
+During that time-window, you can see that the Timelock Recovery mechanism has been triggered (a
+transaction from your wallet to itself is created on the Bitcoin blockchain), and if this
+has happened against your will, you can use your master key to cancel the process (by moving
+the funds elsewhere before the time-window expires).
+
+The implementation of Timelock Recovery is done with two transactions that are signed in advance,
+but broadcast only when needed:
+
+ -
+ An Alert Transaction which sends the funds from the wallet to itself (consolidating the UTXOs).
+
+ -
+ A Recovery Transaction which sends the funds to a secondary wallet of your choice and can
+ be added to the blockchain only X days after the Alert Transaction has been broadcast (and mined).
+
+
+Optionally, this extension will also let you sign-in-advance a Cancellation Transaction which can be
+used to cancel the Timelock Recovery process, by broadcasting it before the time-window expires.
+If the Alert Transaction has been broadcast, the Cancellation Transaction will send the funds again to
+the same wallet, which would invalidate the Recovery Transaction (technically: the Recovery Transaction
+will be seen as a transaction that is trying to spend a UTXO that has already been spent).
+
+Timelock Recovery plans do not require any involvement of a third party.
+However, two precautions should be taken:
+
+ -
+ Due to the way Bitcoin transactions and UTXOs work, spending funds from the wallet might break
+ the entire Timelock Recovery plan.
+
+ -
+ Adding more funds to the wallet will not be covered by the Timelock Recovery plan.
+
+
+
+Therefore it is highly recommended not to use the wallet for any purpose after creating a
+Timelock Recovery plan (other than long-term storage).
+Instead, for daily purposes use a separate wallet (with a seed in a place that
+your loved ones could easily find) and only after accumulating enough funds relevant for long-term
+storage, move them to a new highly secured wallet (i.e. with a long passphrase that only you memorize) for
+which you create a new Timelock Recovery plan (back to the daily-purpose wallet or to your inheritors' wallet).
+
+Each accumulation should be done in a new highly secured wallet, but these are easy to create, i.e. you can
+memorize a long passphrase and add a counter at the end (1 for the first accumlation, 2 for the second, etc.).
+
+For more details, visit: https://timelockrecovery.com.
+
+Before we begin, please note:
+
+ -
+ Please prepare in advance the addresses of your inheritors/backup-wallets.
+
+ -
+ Since we are preparing this recovery plan for the long future, it is hard
+ to estimate what the required mining fees will be.
+ If the fee is too low, your inheritors, who don't have access to the master
+ keys, will not be able to simply "replace-by-fee" and use a higher fee.
+ At the moment of writing this code (year 2025) this is not a big deal, because
+ there are acceleration-services, such as
+
+ mempool.space's accelerator
+ , that allows to boost selected transactions for direct payment.
+ Just in case this service will not be available in the future, the
+ Alert Transaction will send a small amount of 600 sats to each
+ destination address. This will allow advance users to boost the
+ first transaction by spending their unmined UTXO in a mechanism called
+ Child-Pay-For-Parent.
+
+
diff --git a/electrum/plugins/timelock_recovery/manifest.json b/electrum/plugins/timelock_recovery/manifest.json
new file mode 100644
index 000000000..a35fd2322
--- /dev/null
+++ b/electrum/plugins/timelock_recovery/manifest.json
@@ -0,0 +1,8 @@
+{
+ "fullname": "Timelock Recovery Utility",
+ "description": "
This plug-in allows you to create Timelock Recovery Plans for your wallet. See: timelockrecovery.com",
+ "author": "orenz0@protonmail.com",
+ "available_for": ["qt"],
+ "icon":"timelock_recovery_60.png",
+ "version": "0.1.0"
+}
diff --git a/electrum/plugins/timelock_recovery/qt.py b/electrum/plugins/timelock_recovery/qt.py
new file mode 100644
index 000000000..be8320c26
--- /dev/null
+++ b/electrum/plugins/timelock_recovery/qt.py
@@ -0,0 +1,1277 @@
+'''
+
+Timelock Recovery
+
+Copyright:
+ 2025 Oren
+
+Distributed under the MIT software license, see the accompanying
+file LICENCE or http://www.opensource.org/licenses/mit-license.php
+
+'''
+
+import os
+import shutil
+import tempfile
+import uuid
+import json
+import hashlib
+from datetime import datetime
+from functools import partial
+from typing import TYPE_CHECKING, Any, List, Optional, Tuple
+from decimal import Decimal
+
+import qrcode
+from PyQt6.QtPrintSupport import QPrinter
+from PyQt6.QtCore import Qt, QRectF, QMarginsF
+from PyQt6.QtGui import (QImage, QPainter, QFont, QIntValidator, QAction,
+ QPageSize, QPageLayout, QFontMetrics)
+from PyQt6.QtWidgets import (QVBoxLayout, QHBoxLayout, QLabel, QMenu, QCheckBox, QToolButton,
+ QPushButton, QLineEdit, QScrollArea, QGridLayout, QFileDialog, QMessageBox)
+
+from electrum import constants, version
+from electrum.gui.common_qt.util import draw_qr, get_font_id
+from electrum.gui.qt.paytoedit import PayToEdit
+from electrum.payment_identifier import PaymentIdentifierType
+from electrum.plugin import hook
+from electrum.i18n import _
+from electrum.transaction import PartialTxOutput
+from electrum.util import NotEnoughFunds, make_dir
+from electrum.gui.qt.util import ColorScheme, WindowModalDialog, Buttons, HelpLabel
+from electrum.gui.qt.util import read_QIcon_from_bytes, read_QPixmap_from_bytes, WaitingDialog
+from electrum.fee_policy import FeePolicy
+from electrum.gui.qt.fee_slider import FeeSlider, FeeComboBox
+
+from .timelock_recovery import TimelockRecoveryPlugin, TimelockRecoveryContext
+
+
+if TYPE_CHECKING:
+ from electrum.gui.qt import ElectrumGui
+ from electrum.gui.qt.main_window import ElectrumWindow
+
+
+AGREEMENT_TEXT = "I understand that the Timelock Recovery plan will be broken if I keep using this wallet"
+MIN_LOCKTIME_DAYS = 2
+# 0xFFFF * 512 seconds = 388.36 days.
+MAX_LOCKTIME_DAYS = 388
+
+
+def selectable_label(text: str) -> QLabel:
+ label = QLabel(text)
+ label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
+ return label
+
+
+class FontManager:
+ def __init__(self, font_name: str, resolution: int):
+ pixels_per_point = resolution / 72.0
+ self.header_font = QFont(font_name, 8)
+ self.header_line_spacing = QFontMetrics(self.header_font).lineSpacing() * pixels_per_point
+ self.title_font = QFont(font_name, 18, QFont.Weight.Bold)
+ self.title_line_spacing = QFontMetrics(self.title_font).height() * pixels_per_point
+ self.subtitle_font = QFont(font_name, 10)
+ self.subtitle_line_spacing = QFontMetrics(self.subtitle_font).height() * pixels_per_point
+ self.title_small_font = QFont(font_name, 16, QFont.Weight.Bold)
+ self.title_small_line_spacing = QFontMetrics(self.title_small_font).height() * pixels_per_point
+ self.body_font = QFont(font_name, 9)
+ self.body_small_font = QFont(font_name, 8)
+ self.body_small_line_spacing = QFontMetrics(self.body_small_font).lineSpacing() * pixels_per_point
+
+
+class Plugin(TimelockRecoveryPlugin):
+ base_dir: str
+ _init_qt_received: bool
+ font_name: str
+ small_logo_bytes: bytes
+ large_logo_bytes: bytes
+ intro_text: str
+
+ def __init__(self, parent, config, name: str):
+ TimelockRecoveryPlugin.__init__(self, parent, config, name)
+ self.base_dir = os.path.join(config.electrum_path(), 'timelock_recovery')
+ make_dir(self.base_dir)
+
+ self._init_qt_received = False
+ self.font_name = 'Monospace'
+ self.small_logo_bytes = self.read_file("timelock_recovery_60.png")
+ self.large_logo_bytes = self.read_file("timelock_recovery_820.png")
+ self.intro_text = self.read_file("intro.txt").decode('utf-8')
+ plugin_metadata: Optional[dict] = parent.get_metadata('timelock_recovery')
+ self.plugin_version: str = plugin_metadata['version'] if plugin_metadata else 'unknown'
+
+ @hook
+ def load_wallet(self, wallet, window):
+ if self._init_qt_received: # only need/want the first signal
+ return
+ self._init_qt_received = True
+ # load custom fonts (note: here, and not in __init__, as it needs the QApplication to be created)
+ if get_font_id('PTMono-Regular.ttf') >= 0 and get_font_id('PTMono-Bold.ttf') >= 0:
+ self.font_name = 'PT Mono'
+
+ @hook
+ def init_menubar(self, window):
+ m = window.wallet_menu.addAction('Timelock Recovery', lambda: self.setup_dialog(window))
+ icon = read_QIcon_from_bytes(self.read_file('timelock_recovery_60.png'))
+ m.setIcon(icon)
+
+ def setup_dialog(self, main_window: 'ElectrumWindow') -> bool:
+ context = TimelockRecoveryContext(main_window.wallet)
+ context.main_window = main_window
+ return self.create_plan_dialog(context)
+
+ def create_intro_dialog(self, context: TimelockRecoveryContext) -> bool:
+ intro_dialog = WindowModalDialog(context.main_window, "Timelock Recovery")
+ intro_dialog.setContentsMargins(11, 11, 1, 1)
+
+ # Create an HBox layout. The logo will be on the left and the rest of the dialog on the right.
+ hbox_layout = QHBoxLayout(intro_dialog)
+
+ # Create the logo label.
+ logo_label = QLabel()
+
+ # Set the logo label pixmap.
+ logo_label.setPixmap(read_QPixmap_from_bytes(self.small_logo_bytes))
+
+ # Align the logo label to the top left.
+ logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
+
+ # Create a VBox layout for the main contents of the dialog.
+ vbox_layout = QVBoxLayout()
+
+ # Populate the HBox layout with spacing between the two columns.
+ hbox_layout.addWidget(logo_label)
+ hbox_layout.addSpacing(16)
+ hbox_layout.addLayout(vbox_layout)
+
+ title_label = QLabel(_("What Is Timelock Recovery?"))
+ vbox_layout.addWidget(title_label)
+
+ intro_label = QLabel(self.intro_text)
+ intro_label.setWordWrap(True)
+ intro_label.setTextFormat(Qt.TextFormat.RichText)
+ intro_label.setOpenExternalLinks(True)
+ intro_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
+
+ intro_wrapper = QScrollArea()
+ intro_wrapper.setWidget(intro_label)
+ intro_wrapper.setWidgetResizable(True)
+ intro_wrapper.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
+ intro_wrapper.setFrameStyle(0)
+ intro_wrapper.setMinimumHeight(200)
+
+ vbox_layout.addWidget(intro_wrapper)
+
+ close_button = QPushButton(_("Close"), intro_dialog)
+ close_button.clicked.connect(intro_dialog.close)
+ vbox_layout.addLayout(Buttons(close_button))
+
+ # Add stretches to the end of the layouts to prevent the contents from spreading when the dialog is enlarged.
+ hbox_layout.addStretch(1)
+ vbox_layout.addStretch(1)
+
+ return bool(intro_dialog.exec())
+
+
+ def create_plan_dialog(self, context: TimelockRecoveryContext) -> bool:
+ plan_dialog = WindowModalDialog(context.main_window, "Timelock Recovery")
+ plan_dialog.setContentsMargins(11, 11, 1, 1)
+ plan_dialog.resize(800, plan_dialog.height())
+
+ fee_policy = FeePolicy(context.main_window.config.FEE_POLICY)
+ create_cancel_cb = QCheckBox('', checked=False)
+ alert_tx_label = QLabel('')
+ recovery_tx_label = QLabel('')
+ cancellation_tx_label = QLabel('')
+
+ if not context.get_alert_address():
+ plan_dialog.show_error(''.join([
+ _("No more addresses in your wallet."), " ",
+ _("You are using a non-deterministic wallet, which cannot create new addresses."), " ",
+ _("If you want to create new addresses, use a deterministic wallet instead."),
+ ]))
+ plan_dialog.close()
+ return
+
+ plan_grid = QGridLayout()
+ plan_grid.setSpacing(8)
+ grid_row = 0
+
+ help_button = QPushButton(_("Help"))
+ help_button.clicked.connect(lambda: self.create_intro_dialog(context))
+
+ next_button = QPushButton(_("Next"), plan_dialog)
+ next_button.clicked.connect(plan_dialog.close)
+ next_button.clicked.connect(lambda: self.start_plan(context))
+ next_button.setEnabled(False)
+
+ payto_e = PayToEdit(context.main_window.send_tab) # Reuse configuration from send tab
+ payto_e.toggle_paytomany()
+
+ context.timelock_days = 90
+ timelock_days_widget = QLineEdit()
+ timelock_days_widget.setValidator(QIntValidator(2, 388))
+ timelock_days_widget.setText(str(context.timelock_days))
+
+ def update_transactions():
+ is_valid = self._validate_input_values(
+ context=context,
+ payto_e=payto_e,
+ timelock_days_widget=timelock_days_widget,
+ )
+ if not is_valid:
+ view_alert_tx_button.setEnabled(False)
+ view_recovery_tx_button.setEnabled(False)
+ view_cancellation_tx_button.setEnabled(False)
+ next_button.setEnabled(False)
+ return
+ try:
+ context.alert_tx = context.make_unsigned_alert_tx(fee_policy)
+ assert all(tx_input.is_segwit() for tx_input in context.alert_tx.inputs())
+ alert_tx_label.setText(_("Fee: {}").format(self.config.format_amount_and_units(context.alert_tx.get_fee())))
+ context.recovery_tx = context.make_unsigned_recovery_tx(fee_policy)
+ assert all(tx_input.is_segwit() for tx_input in context.recovery_tx.inputs())
+ recovery_tx_label.setText(_("Fee: {}").format(self.config.format_amount_and_units(context.recovery_tx.get_fee())))
+ if create_cancel_cb.isChecked():
+ context.cancellation_tx = context.make_unsigned_cancellation_tx(fee_policy)
+ assert all(tx_input.is_segwit() for tx_input in context.cancellation_tx.inputs())
+ cancellation_tx_label.setText(_("Fee: {}").format(self.config.format_amount_and_units(context.cancellation_tx.get_fee())))
+ else:
+ context.cancellation_tx = None
+ except NotEnoughFunds:
+ view_alert_tx_button.setEnabled(False)
+ view_recovery_tx_button.setEnabled(False)
+ view_cancellation_tx_button.setEnabled(False)
+ payto_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
+ payto_e.setToolTip("Not enough funds to create the transactions.")
+ next_button.setEnabled(False)
+ return
+ view_alert_tx_button.setEnabled(True)
+ view_recovery_tx_button.setEnabled(True)
+ view_cancellation_tx_button.setEnabled(True)
+ payto_e.setStyleSheet(ColorScheme.GREEN.as_stylesheet(True))
+ payto_e.setToolTip("")
+ next_button.setEnabled(True)
+
+
+ payto_e.paymentIdentifierChanged.connect(update_transactions)
+ timelock_days_widget.textChanged.connect(update_transactions)
+
+ plan_grid.addWidget(HelpLabel(
+ _("Recipient of the funds"),
+ (
+ _("Recipient of the funds, after the cancellation time window has expired")
+ + "\n\n"
+ + _("This field must contain a single Bitcoin address, or multiple lines in the format: 'address, amount'.") + "\n"
+ + "\n"
+ + _("If multiple lines are used, at least one line must be set to 'max', using the '!' special character.") + "\n"
+ + _("Integers weights can also be used in conjunction with '!', "
+ "e.g. set one amount to '2!' and another to '3!' to split your coins 40-60.")
+ ),
+ ), grid_row, 0)
+ plan_grid.addWidget(payto_e, grid_row, 1, 1, 4)
+ grid_row += 1
+
+ plan_grid.addWidget(HelpLabel(
+ _("Cancellation time-window (days)"),
+ (
+ _("After broadcasting the Alert Transaction, you have a limited time to cancel the transaction.") + "\n"
+ + _("Value must be between {} and {} days.").format(MIN_LOCKTIME_DAYS, MAX_LOCKTIME_DAYS)
+ )
+ ), grid_row, 0)
+ plan_grid.addWidget(timelock_days_widget, grid_row, 1)
+ grid_row += 1
+ plan_grid.addWidget(HelpLabel(
+ _('Create a cancellation transaction'),
+ '\n'.join([
+ _(
+ "If the Alert transaction is has been broadcast against your intention," +
+ " you will be able to broadcast the Cancellation transaction within {} days," +
+ " to invalidate the Recovery transaction and keep the funds in this wallet" +
+ " - without the need to restore the seed of this wallet (i.e. in case you have split or hidden it)."
+ ).format(context.timelock_days),
+ _(
+ "However, if the seed of this wallet is lost, broadcasting the Cancellation transaction" +
+ " might lock the funds on this wallet forever."
+ )
+ ])
+ ), grid_row, 0)
+ plan_grid.addWidget(create_cancel_cb, grid_row, 1, 1, 4)
+ grid_row += 1
+
+ fee_slider = FeeSlider(
+ parent=plan_dialog, network=context.main_window.network,
+ fee_policy=fee_policy,
+ callback=lambda x: update_transactions()
+ )
+
+ fee_combo = FeeComboBox(fee_slider)
+ plan_grid.addWidget(QLabel('Fee policy'), grid_row, 0)
+ plan_grid.addWidget(fee_slider, grid_row, 1)
+ plan_grid.addWidget(fee_combo, grid_row, 2)
+ grid_row += 1
+
+ plan_grid.addWidget(QLabel('Alert transaction'), grid_row, 0)
+ plan_grid.addWidget(alert_tx_label, grid_row, 1, 1, 3)
+ view_alert_tx_button = QPushButton(_('View'))
+ view_alert_tx_button.clicked.connect(lambda: context.main_window.show_transaction(context.alert_tx, show_sign_button=False, show_broadcast_button=False))
+ plan_grid.addWidget(view_alert_tx_button, grid_row, 4)
+ grid_row += 1
+
+ plan_grid.addWidget(QLabel('Recovery transaction'), grid_row, 0)
+ plan_grid.addWidget(recovery_tx_label, grid_row, 1, 1, 3)
+ view_recovery_tx_button = QPushButton(_('View'))
+ view_recovery_tx_button.clicked.connect(lambda: context.main_window.show_transaction(context.recovery_tx, show_sign_button=False, show_broadcast_button=False))
+ plan_grid.addWidget(view_recovery_tx_button, grid_row, 4)
+ grid_row += 1
+
+ cancellation_label = QLabel('Cancellation transaction')
+ plan_grid.addWidget(cancellation_label, grid_row, 0)
+ plan_grid.addWidget(cancellation_tx_label, grid_row, 1, 1, 3)
+ view_cancellation_tx_button = QPushButton(_('View'))
+ view_cancellation_tx_button.clicked.connect(lambda: context.main_window.show_transaction(context.cancellation_tx, show_sign_button=False, show_broadcast_button=False))
+ plan_grid.addWidget(view_cancellation_tx_button, grid_row, 4)
+ grid_row += 1
+
+ plan_grid.setRowStretch(grid_row, 1) # Make sure the grid does not stretch
+ # Create an HBox layout. The logo will be on the left and the rest of the dialog on the right.
+ hbox_layout = QHBoxLayout(plan_dialog)
+
+ def on_cb_change(x):
+ cancellation_label.setVisible(x)
+ cancellation_tx_label.setVisible(x)
+ view_cancellation_tx_button.setVisible(x)
+ update_transactions()
+ create_cancel_cb.stateChanged.connect(on_cb_change)
+
+ logo_label = QLabel()
+ logo_label.setPixmap(read_QPixmap_from_bytes(self.small_logo_bytes))
+ logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
+
+ # Create a VBox layout for the main contents of the dialog.
+ vbox_layout = QVBoxLayout()
+ vbox_layout.addLayout(plan_grid, stretch=1)
+ vbox_layout.addLayout(Buttons(help_button, next_button))
+
+ # Populate the HBox layout.
+ hbox_layout.addWidget(logo_label)
+ hbox_layout.addSpacing(16)
+ hbox_layout.addLayout(vbox_layout, stretch=1)
+
+ # initialize
+ on_cb_change(False)
+
+ return bool(plan_dialog.exec())
+
+ def _validate_input_values(
+ self,
+ context: TimelockRecoveryContext,
+ payto_e: PayToEdit,
+ timelock_days_widget: QLineEdit) -> bool:
+ context.timelock_days = None
+ try:
+ timelock_days_str = timelock_days_widget.text()
+ timelock_days = int(timelock_days_str)
+ if str(timelock_days) != timelock_days_str or timelock_days < MIN_LOCKTIME_DAYS or timelock_days > MAX_LOCKTIME_DAYS:
+ raise ValueError("Timelock Days value not in range.")
+ context.timelock_days = timelock_days
+ timelock_days_widget.setStyleSheet(None)
+ timelock_days_widget.setToolTip("")
+ except ValueError:
+ timelock_days_widget.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
+ timelock_days_widget.setToolTip("Value must be between {} and {} days.".format(MIN_LOCKTIME_DAYS, MAX_LOCKTIME_DAYS))
+ return False
+ pi = payto_e.payment_identifier
+ if not pi:
+ return False
+ if not pi.is_valid():
+ # Don't make background red - maybe the user did not complete typing yet.
+ payto_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True) if '\n' in pi.text.strip() else '')
+ payto_e.setToolTip((pi.get_error() or "Invalid address.") if pi.text else "")
+ return False
+ elif pi.is_multiline():
+ if not pi.is_multiline_max():
+ payto_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
+ payto_e.setToolTip("At least one line must be set to max spend ('!' in the amount column).")
+ return False
+ context.outputs = pi.multiline_outputs
+ else:
+ if not pi.is_available() or pi.type != PaymentIdentifierType.SPK or not pi.spk_is_address:
+ payto_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
+ payto_e.setToolTip("Invalid address type - must be a Bitcoin address.")
+ return False
+ scriptpubkey, is_address = pi.parse_output(pi.text.strip())
+ if not is_address:
+ payto_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
+ payto_e.setToolTip("Must be a valid address, not a script.")
+ return False
+ context.outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value='!')]
+ return True
+
+ def start_plan(self, context: TimelockRecoveryContext):
+ main_window = context.main_window
+ wallet = main_window.wallet
+ password = main_window.get_password()
+
+ def task():
+ wallet.sign_transaction(context.alert_tx, password, ignore_warnings=True)
+ context.add_input_info()
+ wallet.sign_transaction(context.recovery_tx, password, ignore_warnings=True)
+ if context.cancellation_tx is not None:
+ wallet.sign_transaction(context.cancellation_tx, password, ignore_warnings=True)
+
+ def on_success(result):
+ self.create_download_dialog(context)
+ def on_failure(exc_info):
+ main_window.on_error(exc_info)
+ msg = _('Signing transaction...')
+ WaitingDialog(main_window, msg, task, on_success, on_failure)
+
+
+ def create_download_dialog(self, context: TimelockRecoveryContext) -> bool:
+ context.recovery_plan_id = str(uuid.uuid4())
+ context.recovery_plan_created_at = datetime.now().astimezone()
+ download_dialog = WindowModalDialog(context.main_window, "Timelock Recovery - Download")
+ download_dialog.setContentsMargins(11, 11, 1, 1)
+ download_dialog.resize(800, download_dialog.height())
+
+ # Create an HBox layout. The logo will be on the left and the rest of the dialog on the right.
+ hbox_layout = QHBoxLayout(download_dialog)
+
+ # Create the logo label
+ logo_label = QLabel()
+ logo_label.setPixmap(read_QPixmap_from_bytes(self.small_logo_bytes))
+ logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
+
+ # Create a VBox layout for the main contents
+ vbox_layout = QVBoxLayout()
+
+ # Create and populate the grid
+ grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.setColumnStretch(3, 1)
+
+ line_number = 0
+
+ # Add Recovery Plan ID row
+ grid.addWidget(HelpLabel(
+ _("Recovery Plan ID"),
+ _("Unique identifier for this recovery plan"),
+ ), 0, 0)
+ grid.addWidget(selectable_label(context.recovery_plan_id), line_number, 1, 1, 4)
+ line_number += 1
+ # Add Creation Date row
+ grid.addWidget(HelpLabel(
+ _("Created At"),
+ _("Date and time when this recovery plan was created"),
+ ), 1, 0)
+ grid.addWidget(selectable_label(context.recovery_plan_created_at.strftime("%Y-%m-%d %H:%M:%S %Z (%z)")), line_number, 1, 1, 4)
+ line_number += 1
+
+ grid.addWidget(HelpLabel(
+ _("Alert Address"),
+ _("This address in your wallet will receive the funds when the Alert Transaction is broadcast."),
+ ), line_number, 0)
+ alert_address = context.get_alert_address()
+ grid.addWidget(selectable_label(alert_address), line_number, 1, 1, 3)
+ copy_button = QPushButton(_("Copy"))
+ copy_button.clicked.connect(lambda: context.main_window.do_copy(alert_address))
+ grid.addWidget(copy_button, line_number, 4)
+ line_number += 1
+
+ if context.cancellation_tx is not None:
+ cancellation_address = context.get_cancellation_address()
+ grid.addWidget(HelpLabel(
+ _("Cancellation Address"),
+ _("This address in your wallet will receive the funds when the Cancellation transaction is broadcast."),
+ ), line_number, 0)
+ grid.addWidget(selectable_label(cancellation_address), line_number, 1, 1, 3)
+ copy_button2 = QPushButton(_("Copy"))
+ copy_button2.clicked.connect(lambda: context.main_window.do_copy(cancellation_address))
+ grid.addWidget(copy_button2, line_number, 4)
+ line_number += 1
+
+ grid.addWidget(HelpLabel(
+ _("Alert Transaction ID"),
+ _("ID of the Alert transaction"),
+ ), line_number, 0)
+ grid.addWidget(selectable_label(context.alert_tx.txid()), line_number, 1, 1, 3)
+ line_number += 1
+
+ grid.addWidget(HelpLabel(
+ _("Recovery Transaction ID"),
+ _("ID of the Recovery transaction"),
+ ), line_number, 0)
+ grid.addWidget(selectable_label(context.recovery_tx.txid()), line_number, 1, 1, 4)
+ line_number += 1
+
+ if context.cancellation_tx is not None:
+ grid.addWidget(HelpLabel(
+ _("Cancellation Transaction ID"),
+ _("ID of the Cancellation transaction"),
+ ), line_number, 0)
+ grid.addWidget(selectable_label(context.cancellation_tx.txid()), line_number, 1, 1, 4)
+ line_number += 1
+
+ grid.setRowStretch(line_number, 1)
+ # Create butttons
+ recovery_menu = QMenu()
+ action = QAction('Save as PDF', recovery_menu)
+ action.triggered.connect(partial(self._save_recovery_plan_pdf, context, download_dialog))
+ recovery_menu.addAction(action)
+ action = QAction('Save as JSON', recovery_menu)
+ action.triggered.connect(partial(self._save_recovery_plan_json, context, download_dialog))
+ recovery_menu.addAction(action)
+ recovery_button = QToolButton()
+ recovery_button.setText(_("Save Recovery Plan"))
+ recovery_button.setMenu(recovery_menu)
+ recovery_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
+ # Save Cancellation Plan button row (if applicable)
+ cancellation_menu = QMenu()
+ action = QAction('Save as PDF', cancellation_menu)
+ action.triggered.connect(partial(self._save_cancellation_plan_pdf, context, download_dialog))
+ cancellation_menu.addAction(action)
+ action = QAction('Save as JSON', cancellation_menu)
+ action.triggered.connect(partial(self._save_cancellation_plan_json, context, download_dialog))
+ cancellation_menu.addAction(action)
+ cancellation_button = QToolButton()
+ cancellation_button.setText(_("Save Cancellation Plan"))
+ cancellation_button.setMenu(cancellation_menu)
+ cancellation_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
+ # Add layouts to main vbox
+ vbox_layout.addLayout(grid)
+ vbox_layout.addStretch()
+ download_hbox = QHBoxLayout()
+ download_hbox.addWidget(recovery_button)
+ if context.cancellation_tx is not None:
+ download_hbox.addWidget(cancellation_button)
+ # agree checkbox
+ def on_agreement(b):
+ recovery_button.setEnabled(bool(b))
+ cancellation_button.setEnabled(bool(b))
+ on_agreement(False)
+ agree_cb = QCheckBox(AGREEMENT_TEXT)
+ agree_cb.stateChanged.connect(on_agreement)
+ vbox_layout.addWidget(agree_cb)
+ vbox_layout.addStretch()
+ vbox_layout.addLayout(download_hbox)
+ close_button = QPushButton(_("Close"), download_dialog)
+ def on_close():
+ if context.cancellation_tx is not None and not context.cancellation_plan_saved:
+ if not context.recovery_plan_saved:
+ is_sure = download_dialog.question(
+ _("Are you sure you want to close this dialog without saving any of the files?"),
+ title=_("Close"),
+ icon=QMessageBox.Icon.Question
+ )
+ if not is_sure:
+ return
+ else:
+ is_sure = download_dialog.question(
+ _("Are you sure you want to close this dialog without saving the cancellation-plan?"),
+ title=_("Close"),
+ icon=QMessageBox.Icon.Question
+ )
+ if not is_sure:
+ return
+ elif not context.recovery_plan_saved:
+ is_sure = download_dialog.question(
+ _("Are you sure you want to close this dialog without saving the recovery-plan?"),
+ title=_("Close"),
+ icon=QMessageBox.Icon.Question
+ )
+ if not is_sure:
+ return
+ download_dialog.close()
+ close_button.clicked.connect(on_close)
+ vbox_layout.addLayout(Buttons(close_button))
+ # Populate the HBox layout.
+ hbox_layout.addWidget(logo_label)
+ hbox_layout.addSpacing(16)
+ hbox_layout.addLayout(vbox_layout, stretch=1)
+
+ return bool(download_dialog.exec())
+
+ @classmethod
+ def _checksum(cls, json_data: dict[str, Any]) -> str:
+ # Assumes the values have a consistent json representation (not a key-value
+ # object whose fields can be ordered in multiple ways).
+ return hashlib.sha256(json.dumps(
+ sorted(json_data.items()),
+ skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=(',', ':'),
+ default=None, sort_keys=False,
+ ).encode()).hexdigest()
+
+ def _save_recovery_plan_json(self, context: TimelockRecoveryContext, download_dialog: WindowModalDialog):
+ try:
+ # Open a Save As dialog to get the file path
+ file_path, _selected_filter = QFileDialog.getSaveFileName(
+ download_dialog,
+ _("Save Recovery Plan JSON..."),
+ os.path.join(self.base_dir, "timelock-recovery-plan-{}.json".format(context.recovery_plan_id)),
+ _("JSON files (*.json)")
+ )
+ if not file_path:
+ return
+ with open(file_path, "w") as json_file:
+ json_data = {
+ "kind": "timelock-recovery-plan",
+ "id": context.recovery_plan_id,
+ "created_at": context.recovery_plan_created_at.isoformat(),
+ "plugin_version": self.plugin_version,
+ "wallet_kind": "Electrum",
+ "wallet_version": version.ELECTRUM_VERSION,
+ "wallet_name": context.wallet_name,
+ "timelock_days": context.timelock_days,
+ "anchor_amount_sats": context.ANCHOR_OUTPUT_AMOUNT_SATS,
+ "anchor_addresses": [output.address for output in context.outputs],
+ "alert_address": context.get_alert_address(),
+ "alert_inputs": [tx_input.prevout.to_str() for tx_input in context.alert_tx.inputs()],
+ "alert_tx": context.alert_tx.serialize().upper(),
+ "alert_txid": context.alert_tx.txid(),
+ "alert_fee": context.alert_tx.get_fee(),
+ "alert_weight": context.alert_tx.estimated_weight(),
+ "recovery_tx": context.recovery_tx.serialize().upper(),
+ "recovery_txid": context.recovery_tx.txid(),
+ "recovery_fee": context.recovery_tx.get_fee(),
+ "recovery_weight": context.recovery_tx.estimated_weight(),
+ "recovery_outputs": [[tx_output.address, tx_output.value] for tx_output in context.recovery_tx.outputs()],
+ }
+ # Simple checksum to ensure the file is not corrupted by foolish users
+ json_data["checksum"] = self._checksum(json_data)
+ json.dump(json_data, json_file, indent=2)
+ download_dialog.show_message(_("File saved successfully"))
+ context.recovery_plan_saved = True
+ except Exception as e:
+ self.logger.exception(repr(e))
+ download_dialog.show_error(_("Error saving file"))
+
+ def _save_cancellation_plan_json(self, context: TimelockRecoveryContext, download_dialog: WindowModalDialog):
+ try:
+ # Open a Save As dialog to get the file path
+ file_path, _selected_filter = QFileDialog.getSaveFileName(
+ download_dialog,
+ _("Save Cancellation Plan JSON..."),
+ os.path.join(self.base_dir, "timelock-cancellation-plan-{}.json".format(context.recovery_plan_id)),
+ _("JSON files (*.json)")
+ )
+ if not file_path:
+ return
+ with open(file_path, "w") as f:
+ json_data = {
+ "kind": "timelock-cancellation-plan",
+ "id": context.recovery_plan_id,
+ "created_at": context.recovery_plan_created_at.isoformat(),
+ "plugin_version": self.plugin_version,
+ "wallet_kind": "Electrum",
+ "wallet_version": version.ELECTRUM_VERSION,
+ "wallet_name": context.wallet_name,
+ "timelock_days": context.timelock_days,
+ "alert_txid": context.alert_tx.txid(),
+ "cancellation_address": context.get_cancellation_address(),
+ "cancellation_tx": context.cancellation_tx.serialize().upper(),
+ "cancellation_txid": context.cancellation_tx.txid(),
+ "cancellation_fee": context.cancellation_tx.get_fee(),
+ "cancellation_weight": context.cancellation_tx.estimated_weight(),
+ "cancellation_amount": context.cancellation_tx.output_value(),
+ }
+ # Simple checksum to ensure the file is not corrupted by foolish users
+ json_data["checksum"] = self._checksum(json_data)
+ json.dump(json_data, f, indent=2)
+ download_dialog.show_message(_("File saved successfully"))
+ context.cancellation_plan_saved = True
+ except Exception as e:
+ self.logger.exception(repr(e))
+ download_dialog.show_error(_("Error saving file"))
+
+ def _create_pdf_printer(self, file_path: str) -> QPrinter:
+ printer = QPrinter()
+ printer.setResolution(600)
+ printer.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
+ printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
+ printer.setOutputFileName(file_path)
+ printer.setPageMargins(QMarginsF(20, 20, 20, 20), QPageLayout.Unit.Point)
+ return printer
+
+ def _paint_scaled_logo(self, painter: QPainter, page_width: int, current_height: float) -> int:
+ logo_pixmap = read_QPixmap_from_bytes(self.large_logo_bytes)
+ logo_size = int(page_width / 10)
+ scaled_logo = logo_pixmap.scaled(
+ logo_size,
+ logo_size,
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation
+ )
+ # Center the logo horizontally and draw at current_height
+ logo_x = (page_width - scaled_logo.width()) / 2
+ painter.drawPixmap(int(logo_x), int(current_height), scaled_logo)
+ return scaled_logo.height()
+
+ def _save_recovery_plan_pdf(self, context: TimelockRecoveryContext, download_dialog: WindowModalDialog):
+ # Open a Save As dialog to get the file path
+ file_path, _selected_filter = QFileDialog.getSaveFileName(
+ download_dialog,
+ _("Save Recovery Plan PDF..."),
+ os.path.join(self.base_dir, "timelock-recovery-plan-{}.pdf".format(context.recovery_plan_id)),
+ _("PDF files (*.pdf)")
+ )
+ if not file_path:
+ return
+
+ painter = QPainter()
+ temp_file_path: Optional[str] = None
+
+ try:
+ with tempfile.NamedTemporaryFile(dir=os.path.dirname(file_path), prefix=f"{os.path.basename(file_path)}-", delete=False) as temp_file:
+ temp_file_path = temp_file.name
+ printer = self._create_pdf_printer(temp_file_path)
+ if not painter.begin(printer):
+ return
+ self._paint_recovery_plan_pdf(context, painter, printer)
+ painter.end()
+ shutil.move(temp_file_path, file_path)
+ download_dialog.show_message(_("File saved successfully"))
+ context.recovery_plan_saved = True
+ except (IOError, MemoryError) as e:
+ self.logger.exception(repr(e))
+ download_dialog.show_error(_("Error saving file"))
+ if temp_file_path is not None and os.path.exists(temp_file_path):
+ os.remove(temp_file_path)
+ finally:
+ if painter.isActive():
+ painter.end()
+
+ def _paint_recovery_plan_pdf(self, context: TimelockRecoveryContext, painter: QPainter, printer: QPrinter):
+ font_manager = FontManager(self.font_name, printer.resolution())
+
+ # Get page dimensions
+ page_rect = printer.pageRect(QPrinter.Unit.DevicePixel)
+ page_width = page_rect.width()
+ page_height = page_rect.height()
+
+ current_height = 0
+ page_number = 1
+
+ # Header
+ painter.setFont(font_manager.header_font)
+ painter.drawText(
+ QRectF(0, 0, page_width, font_manager.header_line_spacing + 20),
+ Qt.AlignmentFlag.AlignHCenter,
+ f"Recovery-Guide Date: {context.recovery_plan_created_at.strftime('%Y-%m-%d %H:%M:%S %Z (%z)')} ID: {context.recovery_plan_id} Page: {page_number}",
+ )
+ current_height += font_manager.header_line_spacing + 40
+
+ current_height += self._paint_scaled_logo(painter, page_width, current_height) + 40
+
+ # Title
+ painter.setFont(font_manager.title_font)
+ painter.drawText(QRectF(0, current_height, page_width, font_manager.title_line_spacing + 20), Qt.AlignmentFlag.AlignHCenter, "Timelock-Recovery Guide")
+ current_height += font_manager.title_line_spacing + 20
+
+ # Subtitle
+ painter.setFont(font_manager.subtitle_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.subtitle_line_spacing + 20), Qt.AlignmentFlag.AlignCenter,
+ f"Electrum Version: {version.ELECTRUM_VERSION} - Plugin Version: {self.plugin_version}"
+ )
+ current_height += font_manager.subtitle_line_spacing + 60
+
+ # Main content
+ recovery_tx_outputs = context.recovery_tx.outputs()
+ painter.setFont(font_manager.body_font)
+ intro_text = (
+ f"This document will guide you through the process of recovering the funds on wallet: {context.wallet_name}. "
+ f"The process will take at least {context.timelock_days} days, and will eventually send the following amount "
+ f"to the following {'address' if len(recovery_tx_outputs) == 1 else 'addresses'}:\n\n"
+ + '\n'.join(f'• {output.address}: {context.main_window.config.format_amount_and_units(output.value)}' for output in recovery_tx_outputs) + "\n\n"
+ f"Before proceeding, MAKE SURE THAT YOU HAVE ACCESS TO THE {'WALLET OF THIS ADDRESS' if len(recovery_tx_outputs) == 1 else 'WALLETS OF THESE ADDRESSES'}, "
+ f"OR TRUST THE {'OWNER OF THIS ADDRESS' if len(recovery_tx_outputs) == 1 else 'OWNERS OF THESE ADDRESSES'}. "
+ "The simplest way to do so is to send a small amount to the address, and then trying "
+ "to send all funds from that wallet to a different wallet. Also important: make sure that the "
+ "seed-phrase of this wallet has not been compromised, or else a malicious actor could steal "
+ "the funds the moment they reach their destination.\n\n"
+ "For more information, visit: https://timelockrecovery.com\n"
+ )
+
+ drawn_rect = painter.drawText(
+ QRectF(0, current_height, page_width, page_height - current_height),
+ Qt.TextFlag.TextWordWrap,
+ intro_text,
+ )
+ current_height += drawn_rect.height() + 20
+
+ # Step 1
+ painter.setFont(font_manager.title_small_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.title_small_line_spacing + 20),Qt.AlignmentFlag.AlignLeft,
+ "Step 1 - Broadcasting the Alert transaction",
+ )
+ current_height += font_manager.title_small_line_spacing + 20
+
+ painter.setFont(font_manager.body_font)
+ # Calculate number of anchors
+ num_anchors = len(context.alert_tx.outputs()) - 1
+
+ # Split alert tx into parts if needed
+ alert_raw = context.alert_tx.serialize().upper()
+ if len(alert_raw) < 2300:
+ alert_raw_parts = [alert_raw]
+ else:
+ alert_raw_parts = []
+ for i in range(0, len(alert_raw), 2100):
+ alert_raw_parts.append(alert_raw[i:i+2100])
+
+ # Step 1 explanation text
+ step1_text = (
+ f"The first step is to broadcast the Alert transaction. "
+ f"This transaction will keep most funds in the same wallet {context.wallet_name}, "
+ )
+
+ if num_anchors > 0:
+ step1_text += (
+ f"except for 600 sats that will be sent to "
+ f"{'each of the following addresses' if num_anchors > 1 else 'the following address'} "
+ f"(and can be used in case you need to accelerate the transaction via Child-Pay-For-Parent, "
+ f"as we'll explain later):\n"
+ )
+ for output in context.alert_tx.outputs():
+ if output.address != context.get_alert_address() and output.value == context.ANCHOR_OUTPUT_AMOUNT_SATS:
+ step1_text += f"• {output.address}\n"
+ else:
+ step1_text += "except for a small fee.\n"
+
+ step1_text += (
+ f"\nTo broadcast the Alert transaction, "
+ f"{'scan the QR code on the next page' if len(alert_raw_parts) <= 1 else f'scan the QR codes on the next {len(alert_raw_parts)} pages, concatenate the contents of the QR codes (without spaces),'} "
+ f"and paste the content in one of the following Bitcoin block-explorer websites:\n"
+ "• https://mempool.space/tx/push\n"
+ "• https://blockstream.info/tx/push\n"
+ "• https://coinb.in/#broadcast\n\n"
+ f"You should then see a success message for broadcasting transaction-id: {context.alert_tx.txid()}"
+ )
+
+ drawn_rect = painter.drawText(
+ QRectF(0, current_height, page_width, page_height - current_height),
+ Qt.TextFlag.TextWordWrap,
+ step1_text
+ )
+ current_height += drawn_rect.height() + 20
+
+ # Generate QR pages for alert tx parts
+ for i, alert_part in enumerate(alert_raw_parts):
+ # Add new page
+ printer.newPage()
+ page_number += 1
+ current_height = 20
+
+ # Header
+ painter.setFont(font_manager.header_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.header_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Recovery-Guide Date: {context.recovery_plan_created_at.strftime('%Y-%m-%d %H:%M:%S %Z (%z)')} ID: {context.recovery_plan_id} Page: {page_number}"
+ )
+ current_height += font_manager.header_line_spacing + 20
+
+ # Title
+ painter.setFont(font_manager.title_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.title_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ "Alert Transaction"
+ )
+ current_height += font_manager.title_line_spacing + 20
+
+ # Transaction ID
+ painter.setFont(font_manager.subtitle_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.subtitle_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Transaction Id: {context.alert_tx.txid()}"
+ )
+ current_height += font_manager.subtitle_line_spacing + 20
+
+ # Part number if multiple parts
+ if len(alert_raw_parts) > 1:
+ painter.setFont(font_manager.subtitle_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.subtitle_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Part {i+1} of {len(alert_raw_parts)}"
+ )
+ current_height += font_manager.subtitle_line_spacing + 20
+
+ # QR Code
+ qr = qrcode.main.QRCode(
+ error_correction=qrcode.constants.ERROR_CORRECT_Q,
+ )
+ qr.add_data(alert_part)
+ qr.make()
+ qr_image = self._paint_qr(qr)
+
+ # Calculate QR position to center it
+ qr_width = int(page_width * 0.6)
+ qr_x = (page_width - qr_width) / 2
+ painter.drawImage(QRectF(qr_x, current_height, qr_width, qr_width), qr_image)
+ current_height += qr_width + 40
+
+ # Raw text below QR
+ painter.setFont(font_manager.body_font)
+ painter.drawText(
+ QRectF(20, current_height, page_width, page_height - current_height),
+ Qt.TextFlag.TextWrapAnywhere,
+ alert_part
+ )
+
+ printer.newPage()
+ page_number += 1
+ current_height = 20
+ # Header
+ painter.setFont(font_manager.header_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.header_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Recovery-Guide Date: {context.recovery_plan_created_at.strftime('%Y-%m-%d %H:%M:%S %Z (%z)')} ID: {context.recovery_plan_id} Page: {page_number}"
+ )
+ current_height += font_manager.header_line_spacing + 20
+
+ # Step 2 page
+ painter.setFont(font_manager.title_small_font)
+ painter.drawText(QRectF(20, current_height, page_width, font_manager.title_small_line_spacing), Qt.AlignmentFlag.AlignLeft, "Step 2 - Waiting for the Alert transaction confirmation")
+ current_height += font_manager.title_small_line_spacing + 20
+
+ painter.setFont(font_manager.body_font)
+ painter.drawText(QRectF(20, current_height, page_width, font_manager.subtitle_line_spacing), Qt.AlignmentFlag.AlignLeft, "You can follow the Alert transaction via any of the following links:")
+ current_height += font_manager.subtitle_line_spacing + 20
+
+ # QR codes and links for transaction tracking
+ for link in [f"https://mempool.space/tx/{context.alert_tx.txid()}", f"https://blockstream.info/tx/{context.alert_tx.txid()}"]:
+ qr = qrcode.main.QRCode(
+ error_correction=qrcode.constants.ERROR_CORRECT_H,
+ )
+ qr.add_data(link)
+ qr.make()
+ qr_image = self._paint_qr(qr)
+
+ qr_width = int(page_width * 0.2)
+ qr_x = (page_width - qr_width) / 2
+ painter.drawImage(QRectF(qr_x, current_height, qr_width, qr_width), qr_image)
+ current_height += qr_width + 20
+
+ painter.setFont(font_manager.body_small_font)
+ painter.drawText(QRectF(0, current_height, page_width, font_manager.body_small_line_spacing), Qt.AlignmentFlag.AlignCenter, link)
+ current_height += font_manager.body_small_line_spacing + 20
+
+ # Explanation text
+ painter.setFont(font_manager.body_font)
+ explanation_text = (
+ "Please wait for a while until the transaction is marked as \"confirmed\" (number of confirmations greater than 0). "
+ "The time that takes a transaction to confirm depends on the fee that it pays, compared to the fee that other "
+ "pending transactions are willing to pay. At the time this document was created, it was hard to predict what a "
+ "reasonable fee would be today. If the transaction is not confirmed after 24 hours, you may try paying to a "
+ "Transaction Acceleration service, such as the one offered by: https://mempool.space.com ."
+ )
+ if len(context.outputs) > 0:
+ explanation_text += (
+ f" Another solution, which may be cheaper but requires more technical skill, would be to use"
+ f"{' one of the wallets that receive 600 sats (addresses mentioned in Step 1),' if len(context.outputs) > 1 else ' the wallet that receive 600 sats (address mentioned in Step 1),'}"
+ " and send a high-fee transaction that includes that 600 sats UTXO (this transaction could also be from the"
+ " wallet to itself). For more information, visit: https://timelockrecovery.com ."
+ )
+
+ drawn_rect = painter.drawText(QRectF(20, current_height, page_width, page_height - current_height), Qt.TextFlag.TextWordWrap, explanation_text)
+ current_height += drawn_rect.height() + 40
+
+ # Step 3 header
+ painter.setFont(font_manager.title_small_font)
+ painter.drawText(QRectF(20, current_height, page_width, font_manager.title_small_line_spacing), Qt.AlignmentFlag.AlignLeft, "Step 3 - Broadcasting the Recovery transaction")
+ current_height += font_manager.title_small_line_spacing + 20
+
+ # Split recovery transaction if needed
+ recovery_raw = context.recovery_tx.serialize().upper()
+ recovery_raw_parts = [recovery_raw[i:i+2100] for i in range(0, len(recovery_raw), 2100)] if len(recovery_raw) > 2300 else [recovery_raw]
+
+ # Step 3 explanation
+ painter.setFont(font_manager.body_font)
+ step3_text = (
+ f"Approximately {context.timelock_days} days after the Alert transaction has been confirmed, you "
+ "will be able to broadcast the second Recovery transaction that will send the funds to the final"
+ f"{' destinations,' if len(recovery_tx_outputs) > 1 else ' destination,'} mentioned on the first page. This can be done using the same websites mentioned in Step 1, but "
+ f"this time you will need to {'scan the QR code on page ' + str(page_number + 1) if len(recovery_raw_parts) <= 1 else 'scan the QR codes on pages ' + str(page_number + 1) + '-' + str(page_number + len(recovery_raw_parts)) + ' and concatenate their content (without spaces)'}. If this transaction remains unconfirmed for a "
+ "long time, you should use the Transaction Acceleration service mentioned on Step 2, or use the "
+ "Child-Pay-For-Parent technique."
+ )
+ painter.drawText(QRectF(20, current_height, page_width, page_height - current_height), Qt.TextFlag.TextWordWrap, step3_text)
+
+ # Recovery transaction pages
+ for i, recovery_part in enumerate(recovery_raw_parts):
+ printer.newPage()
+ page_number += 1
+ current_height = 20
+
+ # Header
+ painter.setFont(font_manager.header_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.header_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Recovery-Guide Date: {context.recovery_plan_created_at.strftime('%Y-%m-%d %H:%M:%S %Z (%z)')} ID: {context.recovery_plan_id} Page: {page_number}"
+ )
+ current_height += font_manager.header_line_spacing + 20
+
+ # Title
+ painter.setFont(font_manager.title_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.title_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ "Recovery Transaction"
+ )
+ current_height += font_manager.title_line_spacing + 20
+
+ # Transaction ID
+ painter.setFont(font_manager.subtitle_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.subtitle_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Transaction Id: {context.recovery_tx.txid()}"
+ )
+ current_height += font_manager.subtitle_line_spacing + 20
+
+ # Part number if multiple parts
+ if len(recovery_raw_parts) > 1:
+ painter.setFont(font_manager.subtitle_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.subtitle_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Part {i+1} of {len(recovery_raw_parts)}"
+ )
+ current_height += font_manager.subtitle_line_spacing + 20
+
+ # QR Code
+ qr = qrcode.main.QRCode(
+ error_correction=qrcode.constants.ERROR_CORRECT_Q,
+ )
+ qr.add_data(recovery_part)
+ qr.make()
+ qr_image = self._paint_qr(qr)
+
+ # Calculate QR position to center it
+ qr_width = int(page_width * 0.6)
+ qr_x = (page_width - qr_width) / 2
+ painter.drawImage(QRectF(qr_x, current_height, qr_width, qr_width), qr_image)
+ current_height += qr_width + 40
+
+ # Raw text below QR
+ painter.setFont(font_manager.body_font)
+ painter.drawText(
+ QRectF(20, current_height, page_width, page_height - current_height),
+ Qt.TextFlag.TextWrapAnywhere,
+ recovery_part
+ )
+
+ def _save_cancellation_plan_pdf(self, context: TimelockRecoveryContext, download_dialog: WindowModalDialog):
+ # Open a Save As dialog to get the file path
+ file_path, _selected_filter = QFileDialog.getSaveFileName(
+ download_dialog,
+ _("Save Cancellation Plan PDF..."),
+ os.path.join(self.base_dir, "timelock-cancellation-plan-{}.pdf".format(context.recovery_plan_id)),
+ _("PDF files (*.pdf)")
+ )
+ if not file_path:
+ return
+
+ painter = QPainter()
+ temp_file_path: Optional[str] = None
+
+ try:
+ with tempfile.NamedTemporaryFile(dir=os.path.dirname(file_path), prefix=f"{os.path.basename(file_path)}-", delete=False) as temp_file:
+ temp_file_path = temp_file.name
+ printer = self._create_pdf_printer(temp_file_path)
+ if not painter.begin(printer):
+ return
+ self._paint_cancellation_plan_pdf(context, painter, printer)
+ painter.end()
+ shutil.move(temp_file_path, file_path)
+ download_dialog.show_message(_("File saved successfully"))
+ context.cancellation_plan_saved = True
+ except (IOError, MemoryError) as e:
+ self.logger.exception(repr(e))
+ download_dialog.show_error(_("Error saving file"))
+ if temp_file_path is not None and os.path.exists(temp_file_path):
+ os.remove(temp_file_path)
+ finally:
+ if painter.isActive():
+ painter.end()
+
+ def _paint_cancellation_plan_pdf(self, context: TimelockRecoveryContext, painter: QPainter, printer: QPrinter):
+ cancellation_raw = context.cancellation_tx.serialize().upper()
+ if len(cancellation_raw) > 2300:
+ # Splitting the cancellation transaction into multiple QR codes is not implemented
+ # because it is unexpected to happen anyways.
+ raise Exception("Cancellation transaction is too large to be saved as a single QR code")
+
+ font_manager = FontManager(self.font_name, printer.resolution())
+
+ # Get page dimensions
+ page_rect = printer.pageRect(QPrinter.Unit.DevicePixel)
+ page_width = page_rect.width()
+ page_height = page_rect.height()
+
+ current_height = 0
+ page_number = 1
+
+ # Header
+ painter.setFont(font_manager.header_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.header_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Cancellation-Guide Date: {context.recovery_plan_created_at.strftime('%Y-%m-%d %H:%M:%S %Z (%z)')} ID: {context.recovery_plan_id} Page: {page_number}"
+ )
+ current_height += font_manager.header_line_spacing + 40
+
+ current_height += self._paint_scaled_logo(painter, page_width, current_height) + 40
+
+ # Title
+ painter.setFont(font_manager.title_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.title_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ "Timelock-Recovery Cancellation Guide"
+ )
+ current_height += font_manager.title_line_spacing + 20
+
+ # Subtitle
+ painter.setFont(font_manager.subtitle_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.subtitle_line_spacing + 20), Qt.AlignmentFlag.AlignCenter,
+ f"Electrum Version: {version.ELECTRUM_VERSION} - Plugin Version: {self.plugin_version}"
+ )
+ current_height += font_manager.subtitle_line_spacing + 60
+
+ # Main text
+ painter.setFont(font_manager.body_font)
+ explanation_text = (
+ f"This document is intended solely for the eyes of the owner of wallet: {context.wallet_name}. "
+ f"The Recovery Guide (the other document) will allow to transfer the funds from this wallet to "
+ f"a different wallet within {context.timelock_days} days. To prevent this from happening accidentally "
+ f"or maliciously by someone who found that document, you should periodically check if the Alert "
+ f"transaction has been broadcast, using a Bitcoin block-explorer website such as:"
+ )
+ drawn_rect = painter.drawText(
+ QRectF(20, current_height, page_width - 40, page_height),
+ Qt.TextFlag.TextWordWrap,
+ explanation_text
+ )
+ current_height += drawn_rect.height() + 40
+
+ # QR codes and links for transaction tracking
+ for link in [f"https://mempool.space/tx/{context.alert_tx.txid()}", f"https://blockstream.info/tx/{context.alert_tx.txid()}"]:
+ qr = qrcode.main.QRCode(
+ error_correction=qrcode.constants.ERROR_CORRECT_H,
+ )
+ qr.add_data(link)
+ qr.make()
+ qr_image = self._paint_qr(qr)
+
+ qr_width = int(page_width * 0.2)
+ qr_x = (page_width - qr_width) / 2
+ painter.drawImage(QRectF(qr_x, current_height, qr_width, qr_width), qr_image)
+ current_height += qr_width + 20
+
+ painter.setFont(font_manager.body_small_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.body_small_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ link
+ )
+ current_height += font_manager.body_small_line_spacing + 20
+
+ # Watch tower text
+ painter.setFont(font_manager.body_font)
+ drawn_rect = painter.drawText(
+ QRectF(20, current_height, page_width - 40, page_height - current_height),
+ Qt.TextFlag.TextWordWrap,
+ "It is also recommended to use a Watch-Tower service that will notify you immediately if the"
+ " Alert transaction has been broadcast. For more details, visit: https://timelockrecovery.com ."
+ )
+ current_height += drawn_rect.height() + 40
+
+ # Cancellation transaction section
+ cancellation_text = (
+ "In case the Alert transaction has been broadcast, and you want to stop the funds from "
+ "leaving this wallet, you can scan the QR code on page 2, and broadcast "
+ "the content using one of the following Bitcoin block-explorer websites:\n\n"
+ "• https://mempool.space/tx/push\n"
+ "• https://blockstream.info/tx/push\n"
+ "• https://coinb.in/#broadcast\n\n"
+ "If the transaction is not confirmed within reasonable time due to a low fee, you will have "
+ "to access the wallet and use Replace-By-Fee/Child-Pay-For-Parent to move the funds to a new "
+ "address on your wallet. (you can also pay to an Acceleration Service such as the one offered "
+ "by https://mempool.space)\n\n"
+ f"IMPORTANT NOTICE: If you lost the keys to access wallet {context.wallet_name} - do not broadcast the "
+ "transaction on page 2! In this case it is recommended to destroy all copies of this document."
+ )
+ painter.drawText(
+ QRectF(20, current_height, page_width - 40, page_height),
+ Qt.TextFlag.TextWordWrap,
+ cancellation_text
+ )
+
+ # New page for cancellation transaction
+ printer.newPage()
+ page_number += 1
+ current_height = 20
+
+ # Header
+ painter.setFont(font_manager.header_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.header_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Cancellation-Guide Date: {context.recovery_plan_created_at.strftime('%Y-%m-%d %H:%M:%S %Z (%z)')} ID: {context.recovery_plan_id} Page: {page_number}"
+ )
+ current_height += font_manager.header_line_spacing + 20
+
+ # Cancellation transaction title
+ painter.setFont(font_manager.title_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.title_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ "Cancellation Transaction"
+ )
+ current_height += font_manager.title_line_spacing + 20
+
+ # Transaction ID
+ painter.setFont(font_manager.subtitle_font)
+ painter.drawText(
+ QRectF(0, current_height, page_width, font_manager.subtitle_line_spacing),
+ Qt.AlignmentFlag.AlignCenter,
+ f"Transaction Id: {context.cancellation_tx.txid()}"
+ )
+ current_height += font_manager.subtitle_line_spacing + 20
+
+ # QR Code for cancellation transaction
+ qr = qrcode.main.QRCode(
+ error_correction=qrcode.constants.ERROR_CORRECT_Q,
+ )
+ qr.add_data(cancellation_raw)
+ qr.make()
+ qr_image = self._paint_qr(qr)
+
+ qr_width = int(page_width * 0.6)
+ qr_x = (page_width - qr_width) / 2
+ painter.drawImage(QRectF(qr_x, current_height, qr_width, qr_width), qr_image)
+ current_height += qr_width + 40
+
+ # Raw transaction text
+ painter.setFont(font_manager.body_font)
+ painter.drawText(
+ QRectF(20, current_height, page_width - 40, page_height),
+ Qt.TextFlag.TextWrapAnywhere,
+ cancellation_raw
+ )
+
+ @classmethod
+ def _paint_qr(cls, qr: qrcode.main.QRCode) -> QImage:
+ k = len(qr.get_matrix())
+ base_img = QImage(k * 5, k * 5, QImage.Format.Format_ARGB32)
+ draw_qr(qr=qr, paint_device=base_img)
+ return base_img
diff --git a/electrum/plugins/timelock_recovery/timelock_recovery.py b/electrum/plugins/timelock_recovery/timelock_recovery.py
new file mode 100644
index 000000000..1368e8c0d
--- /dev/null
+++ b/electrum/plugins/timelock_recovery/timelock_recovery.py
@@ -0,0 +1,150 @@
+from datetime import datetime
+from typing import TYPE_CHECKING, Callable, List, Optional, Sequence, Tuple
+from electrum.bitcoin import address_to_script
+from electrum.plugin import BasePlugin
+from electrum.transaction import PartialTxOutput, PartialTxInput, TxOutpoint
+from electrum.util import bfh
+
+if TYPE_CHECKING:
+ from electrum.gui.qt import ElectrumWindow
+ from electrum.transaction import PartialTransaction, TxOutput
+ from electrum.wallet import Abstract_Wallet
+
+ALERT_ADDRESS_LABEL = "Timelock Recovery Alert Address"
+CANCELLATION_ADDRESS_LABEL = "Timelock Recovery Cancellation Address"
+
+class PartialTxInputWithFixedNsequence(PartialTxInput):
+ _fixed_nsequence: int
+
+ def __init__(self, *args, nsequence: int = 0xfffffffe, **kwargs):
+ self._fixed_nsequence = nsequence
+ super().__init__(*args, **kwargs)
+
+ @property
+ def nsequence(self) -> int:
+ return self._fixed_nsequence
+
+ @nsequence.setter
+ def nsequence(self, value: int):
+ pass # ignore override attempts
+
+class TimelockRecoveryContext:
+ wallet: 'Abstract_Wallet'
+ wallet_name: str
+ main_window: Optional['ElectrumWindow'] = None
+ timelock_days: Optional[int] = None
+ cancellation_address: Optional[str] = None
+ outputs: Optional[List['PartialTxOutput']] = None
+ alert_tx: Optional['PartialTransaction'] = None
+ recovery_tx: Optional['PartialTransaction'] = None
+ cancellation_tx: Optional['PartialTransaction'] = None
+ recovery_plan_id: Optional[str] = None
+ recovery_plan_created_at: Optional[datetime] = None
+ _alert_address: Optional[str] = None
+ _cancellation_address: Optional[str] = None
+ recovery_plan_saved: bool = False
+ cancellation_plan_saved: bool = False
+
+ ANCHOR_OUTPUT_AMOUNT_SATS = 600
+
+ def __init__(self, wallet: 'Abstract_Wallet'):
+ self.wallet = wallet
+ self.wallet_name = str(self.wallet)
+
+ def _get_address_by_label(self, label: str) -> str:
+ unused_addresses = list(self.wallet.get_unused_addresses())
+ for addr in unused_addresses:
+ if self.wallet.get_label_for_address(addr) == label:
+ return addr
+ for addr in unused_addresses:
+ if not self.wallet.is_address_reserved(addr) and not self.wallet.get_label_for_address(addr):
+ self.wallet.set_label(addr, label)
+ return addr
+ if self.wallet.is_deterministic():
+ addr = self.wallet.create_new_address(False)
+ if addr:
+ self.wallet.set_label(addr, label)
+ return addr
+ return ''
+
+ def get_alert_address(self) -> str:
+ if self._alert_address is None:
+ self._alert_address = self._get_address_by_label(ALERT_ADDRESS_LABEL)
+ return self._alert_address
+
+ def get_cancellation_address(self) -> str:
+ if self._cancellation_address is None:
+ self._cancellation_address = self._get_address_by_label(CANCELLATION_ADDRESS_LABEL)
+ return self._cancellation_address
+
+ def make_unsigned_alert_tx(self, fee_policy) -> 'PartialTransaction':
+ alert_tx_outputs = [
+ PartialTxOutput(scriptpubkey=address_to_script(self.get_alert_address()), value='!'),
+ ] + [
+ PartialTxOutput(scriptpubkey=output.scriptpubkey, value=self.ANCHOR_OUTPUT_AMOUNT_SATS)
+ for output in self.outputs
+ ]
+ return self.wallet.make_unsigned_transaction(
+ coins=self.wallet.get_spendable_coins(confirmed_only=False),
+ outputs=alert_tx_outputs,
+ fee_policy=fee_policy,
+ is_sweep=False,
+ )
+
+ def _alert_tx_output(self) -> Tuple[int, 'TxOutput']:
+ tx_outputs: List[Tuple[int, 'TxOutput']] = [
+ (index, tx_output) for index, tx_output in enumerate(self.alert_tx.outputs())
+ if tx_output.address == self.get_alert_address() and tx_output.value != self.ANCHOR_OUTPUT_AMOUNT_SATS
+ ]
+ if len(tx_outputs) != 1:
+ # Safety check - not expected to happen
+ raise ValueError(f"Expected 1 output from the Alert transaction to the Alert Address, but got {len(tx_outputs)}.")
+ return tx_outputs[0]
+
+ def _alert_tx_outpoint(self, out_idx: int) -> TxOutpoint:
+ return TxOutpoint(txid=bfh(self.alert_tx.txid()), out_idx=out_idx)
+
+ def make_unsigned_recovery_tx(self, fee_policy) -> 'PartialTransaction':
+ prevout_index, prevout = self._alert_tx_output()
+ nsequence: int = round(self.timelock_days * 24 * 60 * 60 / 512)
+ if nsequence > 0xFFFF:
+ # Safety check - not expected to happen
+ raise ValueError("Sequence number is too large")
+ nsequence += 0x00400000 # time based lock instead of block-height based lock
+ recovery_tx_input = PartialTxInputWithFixedNsequence(
+ prevout=self._alert_tx_outpoint(prevout_index),
+ nsequence=nsequence,
+ )
+ recovery_tx_input.witness_utxo = prevout
+
+ return self.wallet.make_unsigned_transaction(
+ coins=[recovery_tx_input],
+ outputs=[output for output in self.outputs if output.value != 0],
+ fee_policy=fee_policy,
+ is_sweep=False,
+ )
+
+ def add_input_info(self):
+ self.recovery_tx.inputs()[0].utxo = self.alert_tx
+ if self.cancellation_tx:
+ self.cancellation_tx.inputs()[0].utxo = self.alert_tx
+
+ def make_unsigned_cancellation_tx(self, fee_policy) -> 'PartialTransaction':
+ prevout_index, prevout = self._alert_tx_output()
+ cancellation_tx_input = PartialTxInput(
+ prevout=self._alert_tx_outpoint(prevout_index),
+ )
+ cancellation_tx_input.witness_utxo = prevout
+
+ return self.wallet.make_unsigned_transaction(
+ coins=[cancellation_tx_input],
+ outputs=[
+ PartialTxOutput(scriptpubkey=address_to_script(self.get_cancellation_address()), value='!'),
+ ],
+ fee_policy=fee_policy,
+ is_sweep=False,
+ )
+
+class TimelockRecoveryPlugin(BasePlugin):
+ def __init__(self, parent, config, name):
+ BasePlugin.__init__(self, parent, config, name)
diff --git a/electrum/plugins/timelock_recovery/timelock_recovery_60.png b/electrum/plugins/timelock_recovery/timelock_recovery_60.png
new file mode 100644
index 000000000..c520eeb95
Binary files /dev/null and b/electrum/plugins/timelock_recovery/timelock_recovery_60.png differ
diff --git a/electrum/plugins/timelock_recovery/timelock_recovery_820.png b/electrum/plugins/timelock_recovery/timelock_recovery_820.png
new file mode 100644
index 000000000..935009347
Binary files /dev/null and b/electrum/plugins/timelock_recovery/timelock_recovery_820.png differ
diff --git a/tests/test_timelock_recovery.py b/tests/test_timelock_recovery.py
new file mode 100644
index 000000000..1df32e6a1
--- /dev/null
+++ b/tests/test_timelock_recovery.py
@@ -0,0 +1,121 @@
+from io import StringIO
+import json
+import os, sys
+from electrum.bitcoin import address_to_script
+from electrum.fee_policy import FixedFeePolicy
+from electrum.plugins.timelock_recovery.timelock_recovery import TimelockRecoveryContext
+from electrum.simple_config import SimpleConfig
+from electrum.storage import WalletStorage
+from electrum.transaction import PartialTxOutput
+from electrum.wallet import Wallet
+from electrum.wallet_db import WalletDB
+
+from . import ElectrumTestCase
+
+
+class TestTimelockRecovery(ElectrumTestCase):
+ TESTNET = True
+
+ def setUp(self):
+ super(TestTimelockRecovery, self).setUp()
+ self.config = SimpleConfig({'electrum_path': self.electrum_path})
+
+ self.wallet_path = os.path.join(self.electrum_path, "timelock_recovery_wallet")
+
+ self._saved_stdout = sys.stdout
+ self._stdout_buffer = StringIO()
+ sys.stdout = self._stdout_buffer
+
+ def tearDown(self):
+ super(TestTimelockRecovery, self).tearDown()
+ # Restore the "real" stdout
+ sys.stdout = self._saved_stdout
+
+ def _create_default_wallet(self):
+ with open(os.path.join(os.path.dirname(__file__), "test_timelock_recovery", "default_wallet"), "r") as f:
+ wallet_str = f.read()
+ storage = WalletStorage(self.wallet_path)
+ db = WalletDB(wallet_str, storage=storage, upgrade=True)
+ wallet = Wallet(db, config=self.config)
+ return wallet
+
+ async def test_get_alert_address(self):
+ wallet = self._create_default_wallet()
+
+ context = TimelockRecoveryContext(wallet)
+ alert_address = context.get_alert_address()
+ self.assertEqual(alert_address, 'tb1qchyc02y9mv4xths4je9puc4yzuxt8rfm26ef07')
+
+ async def test_get_cancellation_address(self):
+ wallet = self._create_default_wallet()
+
+ context = TimelockRecoveryContext(wallet)
+ context.get_alert_address()
+ cancellation_address = context.get_cancellation_address()
+ self.assertEqual(cancellation_address, 'tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg')
+
+ async def test_make_unsigned_alert_tx(self):
+ wallet = self._create_default_wallet()
+
+ context = TimelockRecoveryContext(wallet)
+ context.outputs = [
+ PartialTxOutput(scriptpubkey=address_to_script('tb1q4s8z6g5jqzllkgt8a4har94wl8tg0k9m8kv5zd'), value='!'),
+ ]
+
+ alert_tx = context.make_unsigned_alert_tx(fee_policy=FixedFeePolicy(5000))
+ self.assertEqual(alert_tx.version, 2)
+ alert_tx_inputs = [tx_input.prevout.to_str() for tx_input in alert_tx.inputs()]
+ self.assertEqual(alert_tx_inputs, [
+ '59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2:1',
+ '778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4:1',
+ ])
+ alert_tx_outputs = [(tx_output.address, tx_output.value) for tx_output in alert_tx.outputs()]
+ self.assertEqual(alert_tx_outputs, [
+ ('tb1q4s8z6g5jqzllkgt8a4har94wl8tg0k9m8kv5zd', 600),
+ ('tb1qchyc02y9mv4xths4je9puc4yzuxt8rfm26ef07', 743065),
+ ])
+ self.assertEqual(alert_tx.txid(), '01c227f136c4490ec7cb0fe2ba5e44c436f58906b7fc29a83cb865d7e3bfaa60')
+
+ async def test_make_unsigned_recovery_tx(self):
+ wallet = self._create_default_wallet()
+
+ context = TimelockRecoveryContext(wallet)
+ context.outputs = [
+ PartialTxOutput(scriptpubkey=address_to_script('tb1q4s8z6g5jqzllkgt8a4har94wl8tg0k9m8kv5zd'), value='!'),
+ ]
+ context.alert_tx = context.make_unsigned_alert_tx(fee_policy=FixedFeePolicy(5000))
+ context.timelock_days = 90
+
+ recovery_tx = context.make_unsigned_recovery_tx(fee_policy=FixedFeePolicy(5000))
+ self.assertEqual(recovery_tx.version, 2)
+ recovery_tx_inputs = [tx_input.prevout.to_str() for tx_input in recovery_tx.inputs()]
+ self.assertEqual(recovery_tx_inputs, [
+ '01c227f136c4490ec7cb0fe2ba5e44c436f58906b7fc29a83cb865d7e3bfaa60:1',
+ ])
+ self.assertEqual(recovery_tx.inputs()[0].nsequence, 0x00403b54)
+
+ recovery_tx_outputs = [(tx_output.address, tx_output.value) for tx_output in recovery_tx.outputs()]
+ self.assertEqual(recovery_tx_outputs, [
+ ('tb1q4s8z6g5jqzllkgt8a4har94wl8tg0k9m8kv5zd', 738065),
+ ])
+
+ async def test_make_unsigned_cancellation_tx(self):
+ wallet = self._create_default_wallet()
+
+ context = TimelockRecoveryContext(wallet)
+ context.outputs = [
+ PartialTxOutput(scriptpubkey=address_to_script('tb1q4s8z6g5jqzllkgt8a4har94wl8tg0k9m8kv5zd'), value='!'),
+ ]
+ context.alert_tx = context.make_unsigned_alert_tx(fee_policy=FixedFeePolicy(5000))
+
+ cancellation_tx = context.make_unsigned_cancellation_tx(fee_policy=FixedFeePolicy(6000))
+ self.assertEqual(cancellation_tx.version, 2)
+ cancellation_tx_inputs = [tx_input.prevout.to_str() for tx_input in cancellation_tx.inputs()]
+ self.assertEqual(cancellation_tx_inputs, [
+ '01c227f136c4490ec7cb0fe2ba5e44c436f58906b7fc29a83cb865d7e3bfaa60:1',
+ ])
+ self.assertEqual(cancellation_tx.inputs()[0].nsequence, 0xfffffffd)
+ cancellation_tx_outputs = [(tx_output.address, tx_output.value) for tx_output in cancellation_tx.outputs()]
+ self.assertEqual(cancellation_tx_outputs, [
+ ('tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg', 737065),
+ ])
diff --git a/tests/test_timelock_recovery/default_wallet b/tests/test_timelock_recovery/default_wallet
new file mode 100644
index 000000000..a064672c9
--- /dev/null
+++ b/tests/test_timelock_recovery/default_wallet
@@ -0,0 +1,1437 @@
+{
+ "active_forwardings": {},
+ "addr_history": {
+ "tb1q008n3k9xjpcuyx4mlczn9jm2at90ts55yrtynq": [
+ [
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0",
+ 1455208
+ ],
+ [
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f",
+ 1568264
+ ]
+ ],
+ "tb1q02g5nde0heaed0y24rztkedh9nvswknw50h7fx": [],
+ "tb1q069xqa4lej2tljmd8fcvvfedav54nmspvjnfs2": [],
+ "tb1q07ulrxeuu45uqen0clqe85v5en6rf77cxgxsj5": [
+ [
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39",
+ 1346957
+ ],
+ [
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3",
+ 1346978
+ ]
+ ],
+ "tb1q0j2gt4ap2s08cz5vzm5jg87fdeps7x8v4djgrm": [],
+ "tb1q0quewquwhlfgahhsdg0q3r5lmyzufrtp3fzme4": [
+ [
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6",
+ 1414311
+ ],
+ [
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c",
+ 1414562
+ ]
+ ],
+ "tb1q25arh97ze37n6nk74n3js8ls8z7sva3f0d8pnl": [
+ [
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e",
+ 1454548
+ ],
+ [
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0",
+ 1455208
+ ]
+ ],
+ "tb1q3s4hkssd34tyxdlhafthv4muckjtgwuhltu37t": [],
+ "tb1q3t0xcpmzreece8xdxq8k5aaxrt3r623tqldp8n": [],
+ "tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg": [],
+ "tb1q7mhmnxal53vtc5flh69nph44vah5j56eyesjx9": [],
+ "tb1q8evsj0vkzfak2y5qnqx4yf9lty462l7yfhegyd": [],
+ "tb1q8m8pzk9gpjamgrw3y6y8xtfmw754nedldje5q5": [
+ [
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38",
+ 2579583
+ ],
+ [
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4",
+ 2579584
+ ]
+ ],
+ "tb1q99hkhlswfnj8r5wy2xu9a9m0vy8mvffwzhrx6n": [],
+ "tb1q9u3ufsm9ksql8utguap40ch8zpnw83fgzf4z97": [],
+ "tb1qad28sgvvrxjnxdnfjxcuepgzzhzlapgxcwuj0k": [],
+ "tb1qahrz50yej9v7574q9are3urwyqsdcdddmjl9a6": [
+ [
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4",
+ 2579584
+ ]
+ ],
+ "tb1qak6t2hcl3se6epvhlffaprvfjuf37xunnxq7c9": [
+ [
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1",
+ 2415285
+ ],
+ [
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce",
+ 2415285
+ ]
+ ],
+ "tb1qchyc02y9mv4xths4je9puc4yzuxt8rfm26ef07": [],
+ "tb1qcmq7v2zg0jjy5g47k90fqd0h7a4mcyp7f3ly6r": [
+ [
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e",
+ 2528392
+ ]
+ ],
+ "tb1qcwytrrw3wugydlktsh6yvshlk7jwld38akp8l3": [
+ [
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c",
+ 1457134
+ ],
+ [
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f",
+ 1568264
+ ]
+ ],
+ "tb1qd7tjvgttaxzkszzh5ty4yq97r8wscgteejustc": [
+ [
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c",
+ 2349374
+ ],
+ [
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b",
+ 2349377
+ ],
+ [
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69",
+ 2406297
+ ]
+ ],
+ "tb1qdgscjnjm6w59chq0xgghwtq42vfhhn0murqx6hrfz2yaf2yx9v9skh03as": [
+ [
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69",
+ 2406297
+ ],
+ [
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1",
+ 2415285
+ ]
+ ],
+ "tb1qdy4xwmgklqmyrfj336g4f54582zxtm2yhlge8l": [
+ [
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535",
+ 1414311
+ ],
+ [
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6",
+ 1414311
+ ]
+ ],
+ "tb1qf03zdjdnzxwztxs9d3g9ynsvvs5rmjhvtmln35": [],
+ "tb1qfsxllwl2edccpar9jas9wsxd4vhcewlxqwmn0w27kurkme3jvkdqn4msdp": [
+ [
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe",
+ 2425096
+ ],
+ [
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e",
+ 2528392
+ ]
+ ],
+ "tb1qfu50fqxhfkl69n6urjzuhc5ndr96tuaxrh3an8": [
+ [
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f",
+ 2404107
+ ],
+ [
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69",
+ 2406297
+ ]
+ ],
+ "tb1qgcgk7j9kpt2mygmhmnu4zep79cd289t6aely7z": [
+ [
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269",
+ 1583044
+ ],
+ [
+ "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b",
+ 1583044
+ ]
+ ],
+ "tb1qgdp7aa38x3p2kpn2s5486mkvvx2sktnmxkf47e": [
+ [
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce",
+ 2415285
+ ],
+ [
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661",
+ 2418761
+ ]
+ ],
+ "tb1qgqfvg54gaads92a6dhwcgvvfjvnxdtq373guj5": [],
+ "tb1qh0csljush2tad6t0s4qgx4r5t0rzcw9729l7kx": [],
+ "tb1qh878je0rfut79fkudf4mkl8m4cn8uzfsluersy": [],
+ "tb1qjm4rr97lxawx5csc3nu3rxhwnpxqe3s4uhwagu": [
+ [
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3",
+ 1346978
+ ],
+ [
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba",
+ 1414310
+ ]
+ ],
+ "tb1qjpgepu2p6gyff9a92n2mwst4j2wjktra956lcg": [
+ [
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661",
+ 2418761
+ ],
+ [
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe",
+ 2425096
+ ]
+ ],
+ "tb1qjv7a78ea0jp9d793d5ra7mtzkjzezwldz5zvr6": [],
+ "tb1qkneqe450eqxtpr3r5z8aw4234sjmpknm0gxsae": [],
+ "tb1qkp86pkt75ds257snenp3q7vs29pf4g6cmhy2hw": [],
+ "tb1qlclgzsp2tktdl66xuk3je7ztztstxvjatly8wy": [],
+ "tb1qm7ckcjsed98zhvhv3dr56a22w3fehlkxyh4wgd": [
+ [
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c",
+ 1455781
+ ],
+ [
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f",
+ 1568264
+ ]
+ ],
+ "tb1qn7d2x7272lznt5hhk9s07q3cqnrqljnwa55w6c": [
+ [
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0",
+ 1455208
+ ],
+ [
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c",
+ 1457134
+ ]
+ ],
+ "tb1qndaru6pfal030ev296uxwuulxrezaj0j70ceje": [],
+ "tb1qplsf242vay6vavy4eguef855fx3klmp9505g88": [
+ [
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d",
+ 2579582
+ ],
+ [
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38",
+ 2579583
+ ]
+ ],
+ "tb1qq0gdz0vz02ypa3cawlljstrx8cxydhvalcv8wc": [],
+ "tb1qq2tmmcngng78nllq2pvrkchcdukemtj5s6l0zu": [
+ [
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c",
+ 1346870
+ ],
+ [
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39",
+ 1346957
+ ]
+ ],
+ "tb1qr7mjlxgc6at67tx0s8ypa5efx8clc47xh6yjqg": [],
+ "tb1qrdzfu6mlgrxpupd4syxrv77ncku89a0y0vd7f3": [
+ [
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2",
+ 2579584
+ ]
+ ],
+ "tb1qs087qkcawefutkkv8pg037t6txldk5szfntamj": [],
+ "tb1qs3j3j05rjefnjqf0mlpztszg8acz868wnxgz6j": [],
+ "tb1qsd2c4xwg47hnngn2uqg66y5rxz2hp074u9rq6r": [],
+ "tb1qssypacgyt40r8t95myqgrhrdhq93f5y4jmgmqe53w4tlydphcnaqmpm8kr": [
+ [
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38",
+ 2579583
+ ]
+ ],
+ "tb1qt339ksrha0n5a6lwpql778erkm272hxgamdc0u": [],
+ "tb1qtf9mwfv8ux0j90cwtx9nvz9l46jav40sak7ncg": [],
+ "tb1qtqqddqrlg4xj3dzvjnea8wh5zy2fdf3jxl7qhs": [],
+ "tb1qucj6lx6eatgm5396fe539x53cp8zr0yzgclk6q": [],
+ "tb1quhk94rhlsflc4wgxl9qzd6p6wszt30uxt4a0yj": [
+ [
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba",
+ 1414310
+ ],
+ [
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535",
+ 1414311
+ ]
+ ],
+ "tb1qusm48zmlzwr32csxdw4ar7atw260h22c8zq7jk": [
+ [
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea",
+ 1775825
+ ],
+ [
+ "10a2a7695a5b6ba857b10d04ad047b968656c6d84b58ea226dc92c150cb73b2f",
+ 1892447
+ ]
+ ],
+ "tb1qvsvr06h2phnwmzvwxrlkuzfu2ekhjpfpvguyct": [
+ [
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c",
+ 1414562
+ ],
+ [
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e",
+ 1454548
+ ]
+ ],
+ "tb1qvwwxv48k9vch5ddmf83g4fhd0tnx3mt8jp6rka": [],
+ "tb1qwu2d7rjn9yxalg7cjw6phqzk77lf3fmsta8rhu": [
+ [
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f",
+ 1583044
+ ],
+ [
+ "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b",
+ 1583044
+ ]
+ ],
+ "tb1qym6srwn87eu2sa5prkgd2lqva0nh0tr2xkeftp": [],
+ "tb1qzyaz308030saay93zqma0at032vfqa9y0gfge3": []
+ },
+ "addresses": {
+ "change": [
+ "tb1q07ulrxeuu45uqen0clqe85v5en6rf77cxgxsj5",
+ "tb1qjm4rr97lxawx5csc3nu3rxhwnpxqe3s4uhwagu",
+ "tb1quhk94rhlsflc4wgxl9qzd6p6wszt30uxt4a0yj",
+ "tb1qdy4xwmgklqmyrfj336g4f54582zxtm2yhlge8l",
+ "tb1q0quewquwhlfgahhsdg0q3r5lmyzufrtp3fzme4",
+ "tb1qvsvr06h2phnwmzvwxrlkuzfu2ekhjpfpvguyct",
+ "tb1q25arh97ze37n6nk74n3js8ls8z7sva3f0d8pnl",
+ "tb1q008n3k9xjpcuyx4mlczn9jm2at90ts55yrtynq",
+ "tb1qcwytrrw3wugydlktsh6yvshlk7jwld38akp8l3",
+ "tb1qak6t2hcl3se6epvhlffaprvfjuf37xunnxq7c9",
+ "tb1qgdp7aa38x3p2kpn2s5486mkvvx2sktnmxkf47e",
+ "tb1qjpgepu2p6gyff9a92n2mwst4j2wjktra956lcg",
+ "tb1qcmq7v2zg0jjy5g47k90fqd0h7a4mcyp7f3ly6r",
+ "tb1q8m8pzk9gpjamgrw3y6y8xtfmw754nedldje5q5",
+ "tb1qgqfvg54gaads92a6dhwcgvvfjvnxdtq373guj5",
+ "tb1qahrz50yej9v7574q9are3urwyqsdcdddmjl9a6",
+ "tb1q02g5nde0heaed0y24rztkedh9nvswknw50h7fx",
+ "tb1qzyaz308030saay93zqma0at032vfqa9y0gfge3",
+ "tb1q3s4hkssd34tyxdlhafthv4muckjtgwuhltu37t",
+ "tb1qjv7a78ea0jp9d793d5ra7mtzkjzezwldz5zvr6",
+ "tb1qndaru6pfal030ev296uxwuulxrezaj0j70ceje",
+ "tb1qsd2c4xwg47hnngn2uqg66y5rxz2hp074u9rq6r",
+ "tb1qtqqddqrlg4xj3dzvjnea8wh5zy2fdf3jxl7qhs",
+ "tb1q9u3ufsm9ksql8utguap40ch8zpnw83fgzf4z97",
+ "tb1qlclgzsp2tktdl66xuk3je7ztztstxvjatly8wy",
+ "tb1q7mhmnxal53vtc5flh69nph44vah5j56eyesjx9"
+ ],
+ "receiving": [
+ "tb1qq2tmmcngng78nllq2pvrkchcdukemtj5s6l0zu",
+ "tb1qm7ckcjsed98zhvhv3dr56a22w3fehlkxyh4wgd",
+ "tb1qwu2d7rjn9yxalg7cjw6phqzk77lf3fmsta8rhu",
+ "tb1qn7d2x7272lznt5hhk9s07q3cqnrqljnwa55w6c",
+ "tb1qusm48zmlzwr32csxdw4ar7atw260h22c8zq7jk",
+ "tb1qgcgk7j9kpt2mygmhmnu4zep79cd289t6aely7z",
+ "tb1qfu50fqxhfkl69n6urjzuhc5ndr96tuaxrh3an8",
+ "tb1qd7tjvgttaxzkszzh5ty4yq97r8wscgteejustc",
+ "tb1qchyc02y9mv4xths4je9puc4yzuxt8rfm26ef07",
+ "tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg",
+ "tb1qplsf242vay6vavy4eguef855fx3klmp9505g88",
+ "tb1qrdzfu6mlgrxpupd4syxrv77ncku89a0y0vd7f3",
+ "tb1qt339ksrha0n5a6lwpql778erkm272hxgamdc0u",
+ "tb1qtf9mwfv8ux0j90cwtx9nvz9l46jav40sak7ncg",
+ "tb1qf03zdjdnzxwztxs9d3g9ynsvvs5rmjhvtmln35",
+ "tb1qq0gdz0vz02ypa3cawlljstrx8cxydhvalcv8wc",
+ "tb1qkp86pkt75ds257snenp3q7vs29pf4g6cmhy2hw",
+ "tb1q3t0xcpmzreece8xdxq8k5aaxrt3r623tqldp8n",
+ "tb1qr7mjlxgc6at67tx0s8ypa5efx8clc47xh6yjqg",
+ "tb1qkneqe450eqxtpr3r5z8aw4234sjmpknm0gxsae",
+ "tb1qs087qkcawefutkkv8pg037t6txldk5szfntamj",
+ "tb1q069xqa4lej2tljmd8fcvvfedav54nmspvjnfs2",
+ "tb1q0j2gt4ap2s08cz5vzm5jg87fdeps7x8v4djgrm",
+ "tb1qs3j3j05rjefnjqf0mlpztszg8acz868wnxgz6j",
+ "tb1qh878je0rfut79fkudf4mkl8m4cn8uzfsluersy",
+ "tb1qvwwxv48k9vch5ddmf83g4fhd0tnx3mt8jp6rka",
+ "tb1q8evsj0vkzfak2y5qnqx4yf9lty462l7yfhegyd",
+ "tb1q99hkhlswfnj8r5wy2xu9a9m0vy8mvffwzhrx6n",
+ "tb1qh0csljush2tad6t0s4qgx4r5t0rzcw9729l7kx",
+ "tb1qad28sgvvrxjnxdnfjxcuepgzzhzlapgxcwuj0k",
+ "tb1qym6srwn87eu2sa5prkgd2lqva0nh0tr2xkeftp",
+ "tb1qucj6lx6eatgm5396fe539x53cp8zr0yzgclk6q"
+ ]
+ },
+ "channels": {},
+ "fiat_value": {},
+ "forwarding_failures": {},
+ "frozen_addresses": [
+ "tb1qwu2d7rjn9yxalg7cjw6phqzk77lf3fmsta8rhu",
+ "tb1qd7tjvgttaxzkszzh5ty4yq97r8wscgteejustc",
+ "tb1qchyc02y9mv4xths4je9puc4yzuxt8rfm26ef07"
+ ],
+ "frozen_coins": {
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e:0": true
+ },
+ "imported_channel_backups": {},
+ "invoices": {},
+ "keystore": {
+ "derivation": "m/0h",
+ "pw_hash_version": 1,
+ "root_fingerprint": "535e473f",
+ "seed": "9dk",
+ "seed_type": "segwit",
+ "type": "bip32",
+ "xprv": "vprv9FrABTX8HFeSYL9aaMnLRcEkHBbJnBu9foDJaTvcF8SLvHx6uKqL8rtt7kTd66V4QPLfWPaCJMVZa3h9zuzLr7YFZd1uoEevqqyxp66oSbN",
+ "xpub": "vpub5UqWay427dCjkpE3gPKLnkBUqDRoBed1328uNrLDoTyKo6HFSs9agfDMy1VXbVtcuBVRiAZQsPPsPdu1Ge8m8qvNZPyzJ4ecPsf6U1ieW4x"
+ },
+ "labels": {
+ "bcrt1q069xqa4lej2tljmd8fcvvfedav54nmspwm2y8r": "",
+ "bcrt1q07ulrxeuu45uqen0clqe85v5en6rf77cypla9a": "",
+ "bcrt1q0quewquwhlfgahhsdg0q3r5lmyzufrtpnqmkwu": "",
+ "bcrt1q3t0xcpmzreece8xdxq8k5aaxrt3r623tzk5vs6": "",
+ "bcrt1q6k5h4cz6ra8nzhg90xm9wldvadgh0fptfqj60p": "dshsdu",
+ "bcrt1qchyc02y9mv4xths4je9puc4yzuxt8rfmgnqych": "",
+ "bcrt1qd7tjvgttaxzkszzh5ty4yq97r8wscgtemm9au3": "",
+ "bcrt1qdy4xwmgklqmyrfj336g4f54582zxtm2y4k35sk": "",
+ "bcrt1qf03zdjdnzxwztxs9d3g9ynsvvs5rmjhvfjx7xa": "",
+ "bcrt1qfu50fqxhfkl69n6urjzuhc5ndr96tuaxp7gsyw": "",
+ "bcrt1qgcgk7j9kpt2mygmhmnu4zep79cd289t6lsxfft": "dsadsa",
+ "bcrt1qjm4rr97lxawx5csc3nu3rxhwnpxqe3s477hsl4": "",
+ "bcrt1qkneqe450eqxtpr3r5z8aw4234sjmpknmdpla2s": "",
+ "bcrt1qkp86pkt75ds257snenp3q7vs29pf4g6ce7a8q8": "",
+ "bcrt1qm7ckcjsed98zhvhv3dr56a22w3fehlkxx7vrly": "",
+ "bcrt1qn7d2x7272lznt5hhk9s07q3cqnrqljnwladrd3": "dsadsaddsdsa",
+ "bcrt1qplsf242vay6vavy4eguef855fx3klmp9kxd9sw": "a",
+ "bcrt1qq0gdz0vz02ypa3cawlljstrx8cxydhvaa342e3": "",
+ "bcrt1qq2tmmcngng78nllq2pvrkchcdukemtj5jnxz44": "",
+ "bcrt1qr7mjlxgc6at67tx0s8ypa5efx8clc47x4nalhp": "donke",
+ "bcrt1qrdzfu6mlgrxpupd4syxrv77ncku89a0yd95n7c": "",
+ "bcrt1qs087qkcawefutkkv8pg037t6txldk5szt6jsvm": "",
+ "bcrt1qt339ksrha0n5a6lwpql778erkm272hxglj54c4": "",
+ "bcrt1qtf9mwfv8ux0j90cwtx9nvz9l46jav40sll870p": "",
+ "bcrt1quhk94rhlsflc4wgxl9qzd6p6wszt30uxfuyznm": "",
+ "bcrt1qusm48zmlzwr32csxdw4ar7atw260h22c9ten9l": "hihi hi4655",
+ "bcrt1qvsvr06h2phnwmzvwxrlkuzfu2ekhjpfpwp9f0z": "",
+ "bcrt1qwu2d7rjn9yxalg7cjw6phqzk77lf3fmsf57wq4": "ddddd",
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38": "Open channel",
+ "ce0bea6a71ef5d8649e67a3235a1d1f5f8611e47725d54cb16d5f9ebabd92c8a": "Miner fee for sending to BTC address",
+ "d0ae4c76f769dccfa0187633839d704ae803e810fd592e4f2d14178aaacc94d6:1": "JOHN",
+ "d8a07501eb18302f5b4fa0535c1a7a71eee058de1013f0c2e2ceb53ca50bb99c": "dsadsa",
+ "eaa745cef67b98bcabd604598406cd782cfafc99a419c2c7a8a1dde163cf7dab": "44",
+ "fdf0d67d412785e337b2883cf56784345e215513331d966c5ee875ff6d48b38d": "dsjaiodsa",
+ "tb1qcmq7v2zg0jjy5g47k90fqd0h7a4mcyp7f3ly6r": "at some point there was some change here",
+ "tb1qwu2d7rjn9yxalg7cjw6phqzk77lf3fmsta8rhu": "old address 95645"
+ },
+ "lightning_payments": {},
+ "lightning_preimages": {},
+ "lightning_xprv": "vprv9HAix419EKycrbTEJxT1zKCLURqA9LWRHeohrddxQ35QuvfH8fqMurdF4mseJ5oytUCJH5VvhEXSmCTs5SaoT7jEr2hcy7e8uCFLJtuDSme",
+ "notes_text": "",
+ "num_parents": {
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2": 1,
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e": 9,
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4": 3
+ },
+ "onchain_channel_backups": {},
+ "payment_requests": {},
+ "prevouts_by_scripthash": {
+ "08cf1ff744d66d4699c5654e162654bacc04558035de45a2f613abe5329d4286": {
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d:1": 500000
+ },
+ "0dbcae29aaa4fde8a0542b606d89c6ae46350f0fc7a3a957337f4d7ef03b5dd3": {
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38:1": 99810
+ },
+ "0eaed6a38625ac78bde7585d061872cb55609f8d05c2a4b1f4230f1ea0f78a90": {
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535:1": 7596590
+ },
+ "102dcf626801074a661c851fcc3536c2101eaa2ec03ed03a11b9bb9c19741237": {
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269:0": 99811
+ },
+ "143f42f87521ff360780106377c98d2c27599b6028b7f2858fc71636f003eb9e": {
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c:1": 8798
+ },
+ "241de66b34b781feb863a601c2d49a05ebea8ad0c0f6760ea73e502fafba5bf7": {
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3:1": 9598000
+ },
+ "244501478da81fb6a3ffd88d222708dcb6194931136947873cc0fccc55f24661": {
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f:0": 113600
+ },
+ "2a06d09e67dc4c49523796cf899d2e4002429d9e1280456264b79d1c7caac727": {
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d:0": 381400
+ },
+ "3248021fc00b3f196a362dc9f940a25d488a040498a2d08b7a60aee9ac18626c": {
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e:0": 3190000
+ },
+ "327923b903a21b12aa85f694c8c0de952805a78e4a0164631850c5dc607360df": {
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3:0": 200000
+ },
+ "3641e700a31ec0763da7ecdab2809abd7e6c7668414902ea13958f12c5ca9ed1": {
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea:0": 1387695
+ },
+ "3658a8d8b2614ec7814d2973305e174a12f9d4e9cacf84f81b09cf2fa90d6fe2": {
+ "10a2a7695a5b6ba857b10d04ad047b968656c6d84b58ea226dc92c150cb73b2f:0": 99889
+ },
+ "374127d2991c7bf24c117cc4d73058d2f56987ce4591930271ae5b87599a40a2": {
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2:1": 654165
+ },
+ "385645f17d282282564fc60e25d026d1258b7c23074ce18127577fc6e1e5ede1": {
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69:1": 404711
+ },
+ "39c770e551fa2d861b8bf9be640933abbde97b6fd3d5b86e21e815b493745448": {
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba:1": 8597295
+ },
+ "3b64546ad58f7d7d5632bd6ecc6bd8d69bfab794674713dac2a4a260406aa792": {
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661:0": 100000
+ },
+ "3fea64e654f5b0ebf560cd7def07419823d048251e5cba13b9bc1d19fc5570f9": {
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce:0": 403476
+ },
+ "4940877cf9e1587732a4379592b0490273e92379e93e7a024656f02690a7b955": {
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4:0": 5100
+ },
+ "513018b2d303921ccf5194decd9e9c1f2a7f93f687ef301254fcd3a20dea0c71": {
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe:1": 302547
+ },
+ "54f31d22030e3b6ff2ffc9d6fef4a9184ff9bd37285c99bd405b662a4fb4b99c": {
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c:1": 1148200
+ },
+ "5dea1555deacfbc9698f32073bb26ac2b3a6462752816a727af8e2e288d80857": {
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f:0": 17898379
+ },
+ "6abaf5808033282b0d182f1393cd89d8e73245b317ee42896449206f6d67f009": {
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c:0": 1000
+ },
+ "6c9528c720baace52d4daf3af546fc33be00b981618807a99e088d148e3569e9": {
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1:0": 404573
+ },
+ "6d6a947e3b89b4901842539aa1059a8f44dafe5029c39d1bbad63bfdbf98d4f4": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c:0": 10000000
+ },
+ "76bbdc72fdc74fbb0b049d21b0f80045f2a227cde1cd57a00925a437638984b5": {
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f:0": 3099663
+ },
+ "80c018b3fbc6a3d64e21c8a53a4f5b7eb39cb62998272bd6e0f5a5a5b5947bf2": {
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6:1": 6595885
+ },
+ "846dc81dba7a3df5a44c955bc73afc9b51ab7ef56e3170b835ae9d37967e1323": {
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c:1": 6394885
+ },
+ "969c34a4c63b26dada2c5042db47236fe4bf0d1b16736bc644596b9e1a0863f0": {
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c:0": 200000
+ },
+ "9d905269b1d499af60d605b3a3c16afe21eea08dec1b2013f4707bde14b40c97": {
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c:0": 14695224
+ },
+ "a8445ee64c4775b070c64df9b8006788787da9734502ede664c0d17db65f9efc": {
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2:0": 627994
+ },
+ "af4144146cb7abcd1dd2c812bdcc9661b0611a6a70f682167bb683ea0e91b1de": {
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f:1": 135000
+ },
+ "b77873e2123e43f37054e972ae4685ff203b9d35c14989da321d242f8a0d0799": {
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38:2": 400000
+ },
+ "b81fe544d6d281c5aa695c99dba16ba1fd2bd66f5d342ace52ead7c168b36e2f": {
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69:0": 0
+ },
+ "ba80fd5d5ce4a19c5b2750ec73e621dcc46637090dbae62d3f5bc5bb68565360": {
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4:1": 94500
+ },
+ "bc42e29c85e9d6096a1fd59582e91eccd045acaa7683326ea4dc10d579c0cbc4": {
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe:0": 0
+ },
+ "ceb18da9abab996544f4120b0b7fc221581fdf81a7ac4fc1d311ff9c3dd8571a": {
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535:0": 1000000
+ },
+ "cf1356579698ef6bad07be8da12e82f69687505dddffd1f31f70a626347e69bb": {
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39:1": 9799000
+ },
+ "d20f43d6c9c52327eb9ac64c2b9f4b2b54ef197a485936a2bb46b2a68b37168e": {
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b:1": 988000
+ },
+ "d47dd057642f30739fe81d4c56edcbd658ba87445ecf763edfb508b4c615581c": {
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6:0": 1000000
+ },
+ "d5f4d828cfea4006e972fab9e4c23176761621eb98f8b35a4b4a12671ee04453": {
+ "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b:0": 3199293
+ },
+ "dadfddf343dd5eefc2dedbea6f4ad1f48e85852e60d739593ea7d821e59c0893": {
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38:0": 0
+ },
+ "e4dbc5351a813f40d2a14dad58b8337eb7cca5071d54978f0aaae195bcd3dd20": {
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39:0": 200000
+ },
+ "e690891dd2a7bfa84bb93d56a9aef8fb6b4e8cdca9014b1bcfb16ab69d05f5f0": {
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e:0": 302364
+ },
+ "e7f95bc81c2de2279d0193d4bc6f5950fa0df01be599f5aeb4ffb63b29d7d1f7": {
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661:1": 302700
+ },
+ "eac5af8eeb1ff53507f99263ccfc8b8b2a85bf947634293a33c5eb70178d5875": {
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0:1": 3194603
+ },
+ "ebc0e58e3591ec18b2bb8cce5e58dabfa7d121e026ce0683a02ca4990505aafb": {
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e:1": 3204744
+ },
+ "f04bf26fc16a27abaa2f10540da7a24e369e6ec408f2d901202a7c0cd4a6112d": {
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0:0": 10000
+ },
+ "f38506e05a767c8f162b2271551c63a5ddae94e6eb395e441993de38e921fea6": {
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b:0": 160000,
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c:0": 110000
+ },
+ "f58156a910255e946f7cf2c7daf08ce3faeaf0deda0c9f300679ad9517ef7a68": {
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba:0": 1000000
+ },
+ "faf2c38001ff18046f939975176e9ea069090ed1ef4959625f24d31b3368d68f": {
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea:1": 100000
+ },
+ "fe0e7e43ca025de6ec189c121b245ac207f40177385e829fa725dff68e24e2ec": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c:1": 15313760
+ }
+ },
+ "qt-console-history": [],
+ "reserved_addresses": [
+ "tb1qcmq7v2zg0jjy5g47k90fqd0h7a4mcyp7f3ly6r",
+ "tb1qgqfvg54gaads92a6dhwcgvvfjvnxdtq373guj5"
+ ],
+ "seed_version": 57,
+ "spent_outpoints": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c": {
+ "0": "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39"
+ },
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d": {
+ "1": "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38"
+ },
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3": {
+ "1": "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba"
+ },
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39": {
+ "1": "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3"
+ },
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269": {
+ "0": "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b"
+ },
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f": {
+ "1": "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69"
+ },
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea": {
+ "1": "10a2a7695a5b6ba857b10d04ad047b968656c6d84b58ea226dc92c150cb73b2f"
+ },
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0": {
+ "0": "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c",
+ "1": "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f"
+ },
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c": {
+ "0": "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f"
+ },
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c": {
+ "1": "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e"
+ },
+ "4a87be118635dc782e9dd8be26a5be0fa6a1c5267b9db8303f0af85fbf6173ba": {
+ "1": "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f"
+ },
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e": {
+ "0": "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c",
+ "1": "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0"
+ },
+ "60850b8952f6bf52f1fca76e051888637cb38c1c8a85301c7fa5359f76d1703d": {
+ "1": "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c"
+ },
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce": {
+ "0": "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661"
+ },
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6": {
+ "1": "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c"
+ },
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba": {
+ "1": "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535"
+ },
+ "820fb2b07ba74b4ac3974d2ccaaf8096290614fe7ec83266ae028d6d857b1e41": {
+ "1": "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f"
+ },
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661": {
+ "1": "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe"
+ },
+ "8aed48c4e4ab646c1c5692487c2f1a4414221fef1805463fb21f7549b41a0ff0": {
+ "1": "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269"
+ },
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b": {
+ "0": "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69"
+ },
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69": {
+ "1": "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1"
+ },
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535": {
+ "1": "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6"
+ },
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe": {
+ "1": "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e"
+ },
+ "983bbe3d0593b37996821733e53417fd06bcf559adcacbf339dd7729cb19673d": {
+ "0": "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2"
+ },
+ "a3ee0b514e9af4735097072dd06beba9b9ba02a072e242d1cfe0fce6bfad4bad": {
+ "1": "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea"
+ },
+ "aff92372faa2717819781f1b9a8771abd3fa20f5b504b682d264cc9575acca19": {
+ "0": "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d"
+ },
+ "b30af3bfa76e031e5aea6c2c5cb7cbcd57a145697b74e820ba2a3a5858bdc98c": {
+ "1": "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c"
+ },
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c": {
+ "1": "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f"
+ },
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f": {
+ "0": "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b"
+ },
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38": {
+ "1": "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4"
+ },
+ "ca675c39c9f64bce9ede8f551d2b5871da2aa4747322a2f75878ad76b0b645d9": {
+ "1": "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f"
+ },
+ "d1d575990837cfb5b93d92ba4e9b0454b385fe3b865b98088d68b1778b8855e8": {
+ "0": "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c"
+ },
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1": {
+ "0": "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce"
+ },
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c": {
+ "0": "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69",
+ "1": "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b"
+ },
+ "f2dbe14e9f10e068c6bd4a534d8c8a2945c6bcd69cc85b4d5eb37aca4d3b5384": {
+ "0": "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c"
+ }
+ },
+ "stored_height": 2579588,
+ "submarine_swaps": {},
+ "transactions": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c": "01000000000101e855888b77b1688d08985b863bfe85b354049b4eba923db9b5cf37089975d5d10000000000fdffffff0280969800000000001600140297bde2689a3c79ffe050583b62f86f2d9dae5460abe9000000000016001472df47551b6e7e0c8428814d2e572bc5ac773dda024730440220383efa2f0f5b87f8ce5d6b6eaf48cba03bf522b23fbb23b2ac54ff9d9a8f6a8802206f67d1f909f3c7a22ac0308ac4c19853ffca3a9317e1d7e0c88cc3a86853aaac0121035061949222555a0df490978fe6e7ebbaa96332ecb5c266918fd800c0eef736e7358d1400",
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d": "0200000000010119caac7595cc64d282b604b5f520fad3ab71879a1b1f78197871a2fa7223f9af0000000000fdffffff02d8d105000000000016001440bb0029ce2482a73fff367f0f7ea2390d7a1c5f20a10700000000001600140fe095554ce934ceb095ca39949e9449a36fec25024730440220582ac0428398fb55846cb570e26fd78566b4bb67febfc3c835057e74f1e03ee0022022aa084ba38768a4ce451face547fd9217684e5fefebc29853ffc38ee9eed0ee0121031284572b5ebec99319a87ec710d162a9d21e0afa2a89f8a5cbe290f95369a22e7d5c2700",
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3": "01000000000101399d64669c44e99825ddef70e299d85c9235030f9d75c2ac3103d23616c9f10e0100000000feffffff02400d0300000000002200205208fb0ad888e82e4452ab6cbb06a51c09ca36c6f2a86e5e81884de1860cf400307492000000000016001496ea3197df375c6a62188cf9119aee984c0cc6150247304402203e535dd045e4e7d062af6d11888643ddce48649e123abecf6a42f6048f892f18022076b9e21191096e9c1800904e24fce6758a7d93e8795d35c161a334b83e1286860121030644479c1b8c106e2f0a495fe18dc45572b3413bda2db919c1fcf0f77f43241ca18d1400",
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39": "010000000001012ca35f85d1189b6cdc8d0c95fe637c23928fb23dbc492f9d078f85e9ea6444020000000000feffffff02400d0300000000002200203bf21a5a90ce63776ecf8216ad92b0afc831c1d5fd1d213d29f916181aac74a958859500000000001600147fb9f19b3ce569c0666fc7c193d194ccf434fbd80247304402200571cebd2f7845524862e773661d08d3e14b0a1fd109db2f325ae405e25dd83d02200a92d4ecfe49919389eb9754cc60b5af058a167690b876dad6f09f15f1b69b19012102a0507c8bb3d96dfd7731bafb0ae30e6ed10bbadd6a9f9f88eaf0602b9cc99adc8c8d1400",
+ "10a2a7695a5b6ba857b10d04ad047b968656c6d84b58ea226dc92c150cb73b2f": "02000000000101eafcb255247f0572767b990a52274f705dbb4cdc2d7675b1f7cae7b7e4ebdd250100000000fdffffff01318601000000000017a914bff6c01dc206f8fb7062e314e378e9aeae017a5c870247304402203cd1869eb6ce787f4f27e471b58acfd288d57ca55c8c996aba89d1865647408902200f57bf52775d397b4f02ac584bf6bab96335e89a2b2bfa0bb8f4aa98ee62dd66012102fb265cce2019e555fe23fb45e4cbc3d966bb399a52b2e93b808ad7961b426de15ee01c00",
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269": "0200000001f00f1ab449751fb23f460518ef1f2214441a2f7c4892561c6c64abe4c448ed8a010000006b48304502210092443dd340a1caa7791351324aee87b35b6057a7d9237fc95e1f1d029dd2fe2402200cd3cd13551313d186726bd6beb6d386f79d0a978972e5f923303df1700c957d0121030638cdea69f69ff45ed0d97d8a5c41ce7bbe0d774be898143f7a73cb73ad72b0fdffffff01e38501000000000016001446116f48b60ad5b22377dcf951643e2e1aa3957ac3271800",
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f": "02000000000101411e7b856d8d02ae6632c87efe1406299680afca2c4d97c34a4ba77bb0b20f820100000000feffffff02c0bb01000000000016001455d5618cfc2d731d7c3d80b92bd2c87835c1234c580f0200000000001600144f28f480d74dbfa2cf5c1c85cbe29368cba5f3a60247304402201f857d44cbf474275010d4fc996bf78e15b5631ea6557f842c3a3aebdd7fc8be02205e21cabbf5247ad08c62bea14adcde8f0bad722de3473435f500a8fba7ea6c900121031abaf20c4146bdee1ad87cbc56c892cbe93a92fd11614d379f80be5f60b1f3d50aaf2400",
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea": "02000000000101ad4badbfe6fce0cfd142e272a002bab9a9eb6bd02d07975073f49a4e510beea301000000171600142d886eecf1d36b2a317f74ae3ae0088944017d27feffffff02af2c150000000000160014e866546b96bddfa46ae298c8363527531e03573ba086010000000000160014e437538b7f13871562066babd1fbab72b4fba9580247304402202ce3a50013652650b1b93b89aed5d36bab4adc13ab7054aedb908735b0d2923d02202a4a5bd091f88a186b2d9e92ca99f3129c6cd15861eba06d4b6677601fa1fc9a012102c821655842477341331fb3faa44a04547c5c039b8f44f48b1b7c45266f545ea7d0181b00",
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0": "010000000001016ef7ba954c29f9d1aa21d5fb1ff44e2e36d04536ce17ebd15306130f81f2bd4d0100000000fdffffff0210270000000000001600149f9aa3795e57c535d2f7b160ff023804c60fca6eebbe3000000000001600147bcf38d8a69071c21abbfe0532cb6aeacaf5c2940248304502210086c464555baeb19f60d9a8284bbca0c77009e6864e751bd4ca87105d0f9d1acf02207b6d4f3d5142af6fe7636e60967aac294155bdea469bd5c0ac18612e8ed8456d012103cd68ad8bddacdc82a39a82efd7a045f2d9513a9de31049286b696c87a8efbe8767341600",
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f": "02000000000103e00607297fdfc9808759f2042adfe27912e79031bbff6deb627583a65538bc300100000000fdffffff6c4bec256f089c29f5b6bc5c4cd40fdeca1729cc2106a2cc6e0630c071e429400000000000fdffffff5c22c57d029a8b261ebd9290e2ddd58283fd0fe0a8bef084eb39ebc42e3624b40100000000fdffffff018b1b110100000000160014ef8dfaec6a39b7efc77828fc3840a26da11bfe7502473044022073382b79c486c708f16a1e53c8327060c193ba8db376df3c40abd7b2cf0c39c7022028f210de787f1bdc9cf0060e612b1d71b7f36b1f86956b43a53d516f1aeb2657012103ff475c0005da39a068516cbbfe5f0b5fcc126aba81e405728adab8bdd572d9a3024730440220243989034479fd8de480207d59aec4106ff2277f11f7a982509d309ae90e108f02203282db9a7f38d43c2672abe64f0bfd5d28f60b053fcb1b87671d5dbaa987e12301210242c23a20435cd52192581537b2017288b8cdf3fef349100c11a125d9529017e10247304402207712fbcfac6e0011f404945aa1480332da34eeb5ac47623e341a4f9692caa49802202c6ec5e739aa52f675ffefbf4c057ab6b4a357f66902d96b6af5327206c9367f012103834e1e34194508e3fc7db19d3c6964149efd58f8c67548cc765c3284cad24d6707ee1700",
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c": "010000000001036ef7ba954c29f9d1aa21d5fb1ff44e2e36d04536ce17ebd15306130f81f2bd4d0000000000fdffffff3d70d1769f35a57f1c30858a1c8cb37c638818056ea7fcf152bff652890b85600100000000fdffffff84533b4dca7ab35e4d5bc89cd6bcc645298a8c4d534abdc668e0109f4ee1dbf20000000000fdffffff01383be00000000000160014dfb16c4a19694e2bb2ec8b474d754a74539bfec602483045022100dfbf8dde6a9070a13a808d317e4fb6d35906a3b0ba4f53543ddababfa0df631e02204bc0b692c63c500d517ee96df74ac13e0d680898d761895ea80ed7458031dbc5012102462df8e9cd6196186dd4d5444b6a4c7acd006a7d4c59b8b16e7517828fb8cfc302483045022100b59c985153d9a2a27bdac393ec238dfca3dc2dd63bb181c778ad41568ae2017802204b3c71b2e90dbfb054f432ea95c6fefe29d8c2c36ee725a7916b0241ba6409130121030ea1026664c47391f82ff1bb693b1bfc959ec3b3cbc80a8dfd31fc637cad765702483045022100d056da28e2cd316f5e86aaa56298f0f5d1a313cbbad95f65712b9a4a0be1448f022059d8719fece4677da694a7ca1b61a9ee386eb108e58bdf648dbde0db704ad40f01210208b66c5067eb109735768b92a2800aa6844ae7575911298e0fd6ced709987d04a4361600",
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c": "01000000000101a6c78418b9383dfcf1cf93f1b084f4e5106f862c82ac0b602efa2568195778730100000000feffffff02400d03000000000022002005b05ff139f7a25173a8ada54eaf24c7d9764409c829ca792726fb7aff1eb8360594610000000000160014641837eaea0de6ed898e30ff6e093c566d790521024730440220736f7035d58d08d3e7be11edcdbf3d91cbd86cda16b19ec48b12e5257c21c6bb02202987a4976bbc5e759ba8d5dc7dedc56eadb449966c0e2520f9a1392c2342cb6e0121039c25ce87cddec7e7b0a7a4541047cba7554d6d4093965869787ad2eca4be8a55a1951500",
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e": "010000000001013cbef1e09f39646ab1e2ba273edcafa11f9da5ea25ac0fd194ccec51fa4a28410100000000fdffffff02f0ac3000000000001600144616df751811e5114e045a6b7cad889463555c0488e6300000000000160014553a3b97c2cc7d3d4edeace3281ff038bd06762902473044022070000f29d154f419ebe2dc740c80eb89dc0d571d96d0469f629761d7fb13cad9022008c94670cb3e682a5f076c4cd6634b8ec32168cd648c3f6c89597c105cf5368a0121030bd1ee5d34698af47ee3ceb954b3009ecc0614bb64b54d669adfb233a36d2ae8d3311600",
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2": "020000000001013d6719cb2977dd39f3cbcaad59f5bc06fd1734e53317829679b393053dbe3b980000000000fdffffff021a950900000000001600141f81d08aedb71cd788380d642b755ecb961a1edc55fb0900000000001600141b449e6b7f40cc1e05b5810c367bd3c5b872f5e402473044022064462613a5a80a83ed4badc54ff4e6e85e06781a55ebccc1eb253dc865c478cb02204c4bd331205a9cf90094ca60ba8f1367f5246b92595a361b40fafce522ba4dbf01210251700930db16fbda3de7141685dfae05fda67b234ff7c4861562a3ce07f592497f5c2700",
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e": "02000000000101fe13acd33700e2dc135c833b790eb75e1ed71429773b36192315f3fede7f76970100000000aa771980011c9d040000000000160014c6c1e628487ca44a22beb15e9035f7f76bbc103e04004730440220196ae31431605048d0af586af779d903c813abada54fcf35877b2b4569ee27e502204fa7c6a675369fe1ccc3d5d2e658fc4be7fb480d9f3bd6d1049a7c67264189c401483045022100bd1fdd55c6db44fff54bebf3acd649a5e135254f187561761c150521555e3713022042035709c099da8956af5e6806726170f0104cf5f145ec4632a46d806f3c447601475221022aab353e6b25226a3cb38e81dafe8fca3416642847dab7d38aaafe00eb8294ee2103a5bf1debe9c97bb56c7a4316b45bf2f8d35612db2964253ae8efd0f7ca8e1f7052ae1388d520",
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce": "02000000000101e1c8758e0a1e9728001f7c49b49493384fca2257594de3868cebc779a3b77bd50000000000fdffffff0114280600000000001600144343eef6273442ab066a852a7d6ecc61950b2e7b0247304402205f8a63b3c273abd010a9c095d9591767b6a628452c93acb30809a7989fc3bfa602204b5f812b1c7151e279d58dffa29942552dcb2fad9d14dc39790785fa158b6adf012102c0c89554ca035a1d840eb87ae679146d5cca0fac2a57d078811894ed9e3a4b5ab4da2400",
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6": "010000000001013545008007e0d26a52ab3b64217909a77ab1dd06ada0520b66b6710c04162d970100000000fdffffff0240420f00000000001600142e61106d61d1ad4f1e44e79c8d5bc5907d48d3552da5640000000000160014783997038ebfd28edef06a1e088e9fd905c48d610247304402207878752ca1cc79758db2959062b2fd0ae0682e83fb258385a340d636c9b85810022049a0275f3e8dde8ddaffaab5298c44195c1066ebb414fc3d942b57728058ff6a012102be2e344c2f49afa8d734a2f79a09827b038d2f8f6bfa77d5a2e077c22a3fd80aa6941500",
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba": "01000000000101d3b8ef348489a948e31003be75291a6bfe0cf7288a695a9f9931f2ab3249eb030100000000fdffffff0240420f0000000000160014eb23be36ca7b46f3508a042a121b17f36384c4222f2f830000000000160014e5ec5a8eff827f8ab906f94026e83a7404b8bf8602483045022100c61749c026980911afde50838cb72d3484cdd9b9f419d8e6c7647574a52f0a070220471133c1fb6d21a139f91f01e6fd927463d1f5017dd248f2ca6f97363de396d8012103a23dcf3edc18180b0d82b0be8eb1c30ae61597e99e298246e05109b7ba897637a5941500",
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4": "02000000000101384edbd844c41f59ea558802bc2c1507fade902366510037e131850cd63e6bc90100000000fdffffff02ec130000000000001976a9147f20306730683c828f9a3a990193007106aaca9788ac2471010000000000160014edc62a3c999159ea7aa02f4798f06e2020dc35ad0247304402205f8aa5c02cc6d949f11dc6f61b10ffb37a9fbbd5a4dcc840e1048c257a55ae080220153a6e02eb8b12de1f943d0071cef4f710c3406c8a67174ddf491ea94f4104b0012103555bc1ad91fea0ace08ef29d9e1eeaaa6b9fc45d225c60e30d7b8a6ee37ed03e7f5c2700",
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661": "02000000000101ce08f7d7b4b76a57ba5693a31219e4068e02c5fa8e7e57bc1cf50c47625cf56f0000000000fdffffff02a0860100000000002200204915b90ffa7ef166e0818b75a206d7d179efa14af39c23781a083c749aa651a46c9e040000000000160014905190f141d2089497a554d5b74175929d2b2c7d0247304402203c9cbdd54e067f9a45c7795ae570fc49c706bc4174255f934afbaa6bf8e5411f02203b019772e113869efc8573fed181b14e33ea9bfdeb3f495d3ae1e4f8844e3290012103a85bd8a32a699000359752570dd3bc027d1ca9865fe5fa39c7d4aaab785b50cb48e82400",
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b": "020000000001013cf21d22012066ba1c566dad36c1e5d76e4c95acc95b9e357e4d58e0862acaef0100000000feffffff0200710200000000001600146f9726216be985680857a2c95200be19dd0c217960130f0000000000160014a96ad868b42a6944f6adc75b68a4b30a158ad175024730440220235d1cddad375f7d7612c50fa08b2ec1d4772b333795b6cb98754225626757b902200f4a784e44a2a23fd811f9f28e82ee0333068156ba969556aed1f6337337823f0121030ee45e829f5f95c3a0a184897c7fdb687307301164ed55d98411368d962510bd40d92300",
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69": "020000000001037fbdcc64290f40401515f0cf75d761e9d2b80acf70118d6fa923976b0a49b5230100000000feffffff8bf2c1baabcc788c47c5d2da265a823ddecf97250ba0ddebca1f234e98f8098c0000000000feffffff3cf21d22012066ba1c566dad36c1e5d76e4c95acc95b9e357e4d58e0862acaef0000000000feffffff020000000000000000166a14b837667ddbc6acf0464c1eb35b8a2ba485364cd7e72c0600000000002200206a21894e5bd3a85c5c0f3211772c1553137bcdfbe0c06d5c691289d4a8862b0b0247304402206bfc98da12ba0095c12a9929e7b39132ed9f37f8ba3beaf32a479a738f12a66f0220678fa3ac65fbe79801a39931a4d83255bd75c62a76045ddd7f611ad109538b8a012102bb8e9193021c1f704d7ce8577c5fdc7ba22dd05a03b5b5ecd6ef6307dd8a60bc024730440220730470f1b831e706a633091f2bdaa0944e8b80a61db84dfc41aae5e9265809dd022055362b2618d4dee152819fd259db76aca5c7e7cb7f56d7cd6da8d006b44ca3bf01210319d5b935f6f1cd4a1b84645c3f88db404a93baf78451da144a0dc00aaa1d978c024730440220205c9e119f1b2a6951129f8f4a917572ee4c5c7ed55042b1a6e4d787c8d65bdd02204ced0cbfb7bbdb9e89938ebaca90a9a15727c21a03d5d758617aa25077eba4ed01210319d5b935f6f1cd4a1b84645c3f88db404a93baf78451da144a0dc00aaa1d978c98b72400",
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535": "01000000000101ba709d2dafe9a11fdc2c1b235587455baf0415a2cf302f9cb9c046129a9326770100000000fdffffff0240420f0000000000160014e278e5edb47b274f91d137e767190b4d02b09a172eea730000000000160014692a676d16f83641a6518e9154d2b43a8465ed4402473044022074b4e39c36785a2751fa3b944ed1430bab6461714549d32a195132aace1ca507022043c545d8a1d349ecb6cd3d87fd6c73eb5b68e0d2ba99891d72fce278dce090d0012103e0e2b67a3e8244900fa916decb6a9d45787ebe15b882c7a065cd0151d298fb4ea6941500",
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe": "020000000001016136fe76bf2a9528c3eeff3200dd1c84dbbde4d3df9c5571e0f189a0a7f5db870100000000feffffff020000000000000000166a1492e1315ad9862cb68d93417085728e974f2f8bbed39d0400000000002200204c0dffbbeacb7180f46597605740cdab2f8cbbe603b737b95eb7076de632659a0247304402204b95fa4019d2d5427646c04a8b740111da39b91ab8cd8e7976aa99912d1cf7d60220458db68cf3d977cfc5ecb38c193b890a3beae714d3485233f28b52b4db5f7563012102576b988dc3260d1dc5e579d604f6d3663287c933cde4ccd60d6875e9ced8fc5607012500",
+ "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b": "020000000001026962bc75f03d744a58dc43f17c61ea1f5ebf2aa9e80ede831eb2e097e1e142200000000000fdffffff1f657881066bcd13a189eed6825e30061c1062c473124fcbeaa4e8cde5bc48ba0000000000fdffffff013dd13000000000001976a914fb08dfdd325a10708693d8565b93d2eb38373c0188ac0247304402205039ecdcbbd57fb7093b38bfbd599aeed5c085a0812d7d977f1325d63733ea710220281af83a941ca7e1dd99381904905d095413a654f1d9e9997060bd5f61adf8be01210379da8156b704d5dfe4432bc829588c643ec65c5a409763dccc3da211479f4bb702483045022100e0c07bb37d8da22ea0036b835d86512ecaf584f00534b2548855b02c1d49f59802200cd94644caa3e90349a0e22de080037290b75e2a5906333cb46ef5fb8f36ba7e0121027ba4d3ee6471a985307a37d09eb9a0a73c1a31b57616fe3e53a3d6d454002519c3271800",
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c": "02000000000101e00607297fdfc9808759f2042adfe27912e79031bbff6deb627583a65538bc300000000000fdffffff02e803000000000000536a4c504a61637175656c696e652c2049206c6f766520796f75206d6f7265207468616e20616e797468696e672e20596f75206172652065766572797468696e6720746f206d652e204c6f76652c2044796c616e5e22000000000000160014c388b18dd1771046fecb85f44642ffb7a4efb62702483045022100fa3523ed4f12e036b952c8c7aa88dfc663086bd6a951d14a7faaa3fd28587294022017c7984bdcfeed7e25de571f020f65d30ff03e0dce4c6c2514c2ffc208280c80012103c3172a9f8820681c62b8bf28961988a4642b33f4920b9da14b06965c7fff83fded3b1600",
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f": "0200000002ba7361bf5ff80a3f30b89d7b26c5a1a60fbea526bed89d2e78dc358611be874a010000006b483045022100e2886246bf9d98bae1ed94dddb3da345456d4848c02dcd90e0e9357ed609836f0220344890230f0748c7389645e2a71cf693aeb84617822114bc01e15e17960b36cc01210348d667e58824a00bfc719645c0ff685513243f3476b6b05e4f46cf9b8c3146b0fdffffffd945b6b076ad7858f7a2227374a42ada71582b1d558fde9ece4bf6c9395c67ca010000006a473044022047dcdee44123175b7301882c628da72810ff585e30b94c8403aa641ffd6d8825022046ff6818ed60b5dffbe19534ea4b7a275e48aaee183a14eeb66e36d136880c6b012103ef48fb7c170e02b9f6f481e4eb541947ce89709163c4cdef0af65bf8b64b2d8dfdffffff010f4c2f00000000001600147714df0e53290ddfa3d893b41b8056f7be98a770c3271800",
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38": "020000000001017d6f488442fa311ff6e3a1124ff3cf6a974aa13867306f0456c8e73ca5c7be030100000000feffffff030000000000000000166a14cbfdb55e747e0cbdc2328518ae6363cc5f5c3fc0e2850100000000001600143ece1158a80cbbb40dd12688732d3b77a959e5bf801a06000000000022002084081ee1045d5e33acb4d90081dc6db80b14d09596d1b066917557f23437c4fa0247304402205113e656225c09f8b05fb5bef8c48196e2520a988bc89832f8cc806c64dc3dce02204fbb91805640038fb7e73eb2bf5a28a3a015982a1d9dcd591e6bb9c8c103bab9012102fe3de4d6b6bf207134f40ada206ff6e2ed088169d2413db656c8de963a15c6e17e5c2700",
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1": "0200000000010169fb54298cd7f5490eb80c10e80bb8720390f7d96a9382c91c74827edee9fc940100000000ffffffff015d2c060000000000160014edb4b55f1f8c33ac8597fa53d08d8997131f1b93040047304402202033a46f390d2ed27d1e7aaca2c29cc6af20b92a7535ce7bfa9355480a1a83d202204a94016d878e7e25647738ff28884046d27e4ca83287a0fd4e51ad7d899e47ea0147304402201bddd59748ba0a79d73fda2747b9688b86eef9cad625201d26d6591ceebe18ae022014ea8ecb3722b3ab4873476ca77b2d6ae366d2e61af42ead40419221d6192ba301475221031c9cd5530ea64a7e411c21701d5bcac74e55420924110c93706c12ef7184f0a42103672361cd8f92dfac7fb1d1bb8974bf826cf8cc0eaa6fcb23175be9312d5aced652ae00000000",
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c": "020000000001018cc9bd58583a2aba20e8747b6945a157cdcbb75c2c6cea5a1e036ea7bff30ab30100000000feffffff02b0ad0100000000001600146f9726216be985680857a2c95200be19dd0c2179288511000000000016001418d1896a5e9360279ab7d07a65ecc992d9b514c50247304402206941c4f8cdd0f4a907931f4400ed37a310b00425a2cf218f11affd53d70268e50220412b7e34545d3aabe2b9f00b9fa18c84a2d030b2f4244127dca69738abec6fe10121033399307690d676c7faf57ac454ca6399017f45ef5aa3bebac07b8691bb572a073dd92300"
+ },
+ "tx_fees": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c": [
+ null,
+ false,
+ 1
+ ],
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d": [
+ null,
+ false,
+ 1
+ ],
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3": [
+ 1000,
+ true,
+ 1
+ ],
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39": [
+ 1000,
+ true,
+ 1
+ ],
+ "10a2a7695a5b6ba857b10d04ad047b968656c6d84b58ea226dc92c150cb73b2f": [
+ 111,
+ true,
+ 1
+ ],
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269": [
+ null,
+ false,
+ 1
+ ],
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f": [
+ null,
+ false,
+ 1
+ ],
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea": [
+ null,
+ false,
+ 1
+ ],
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0": [
+ 141,
+ true,
+ 1
+ ],
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f": [
+ 246,
+ true,
+ 3
+ ],
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c": [
+ null,
+ false,
+ 3
+ ],
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c": [
+ 1000,
+ true,
+ 1
+ ],
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e": [
+ 141,
+ true,
+ 1
+ ],
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2": [
+ null,
+ false,
+ 1
+ ],
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e": [
+ 183,
+ true,
+ 1
+ ],
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce": [
+ 1097,
+ true,
+ 1
+ ],
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6": [
+ 705,
+ true,
+ 1
+ ],
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba": [
+ 705,
+ true,
+ 1
+ ],
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4": [
+ 210,
+ true,
+ 1
+ ],
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661": [
+ 776,
+ true,
+ 1
+ ],
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b": [
+ null,
+ false,
+ 1
+ ],
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69": [
+ 289,
+ true,
+ 3
+ ],
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535": [
+ 705,
+ true,
+ 1
+ ],
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe": [
+ 153,
+ true,
+ 1
+ ],
+ "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b": [
+ 181,
+ true,
+ 2
+ ],
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c": [
+ 202,
+ true,
+ 1
+ ],
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f": [
+ null,
+ false,
+ 2
+ ],
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38": [
+ 190,
+ true,
+ 1
+ ],
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1": [
+ 138,
+ true,
+ 1
+ ],
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c": [
+ null,
+ false,
+ 1
+ ]
+ },
+ "txi": {
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3": {
+ "tb1q07ulrxeuu45uqen0clqe85v5en6rf77cxgxsj5": {
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39:1": 9799000
+ }
+ },
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39": {
+ "tb1qq2tmmcngng78nllq2pvrkchcdukemtj5s6l0zu": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c:0": 10000000
+ }
+ },
+ "10a2a7695a5b6ba857b10d04ad047b968656c6d84b58ea226dc92c150cb73b2f": {
+ "tb1qusm48zmlzwr32csxdw4ar7atw260h22c8zq7jk": {
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea:1": 100000
+ }
+ },
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0": {
+ "tb1q25arh97ze37n6nk74n3js8ls8z7sva3f0d8pnl": {
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e:1": 3204744
+ }
+ },
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f": {
+ "tb1q008n3k9xjpcuyx4mlczn9jm2at90ts55yrtynq": {
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0:1": 3194603
+ },
+ "tb1qcwytrrw3wugydlktsh6yvshlk7jwld38akp8l3": {
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c:1": 8798
+ },
+ "tb1qm7ckcjsed98zhvhv3dr56a22w3fehlkxyh4wgd": {
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c:0": 14695224
+ }
+ },
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c": {
+ "tb1q0quewquwhlfgahhsdg0q3r5lmyzufrtp3fzme4": {
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6:1": 6595885
+ }
+ },
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e": {
+ "tb1qvsvr06h2phnwmzvwxrlkuzfu2ekhjpfpvguyct": {
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c:1": 6394885
+ }
+ },
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e": {
+ "tb1qfsxllwl2edccpar9jas9wsxd4vhcewlxqwmn0w27kurkme3jvkdqn4msdp": {
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe:1": 302547
+ }
+ },
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce": {
+ "tb1qak6t2hcl3se6epvhlffaprvfjuf37xunnxq7c9": {
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1:0": 404573
+ }
+ },
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6": {
+ "tb1qdy4xwmgklqmyrfj336g4f54582zxtm2yhlge8l": {
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535:1": 7596590
+ }
+ },
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba": {
+ "tb1qjm4rr97lxawx5csc3nu3rxhwnpxqe3s4uhwagu": {
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3:1": 9598000
+ }
+ },
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4": {
+ "tb1q8m8pzk9gpjamgrw3y6y8xtfmw754nedldje5q5": {
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38:1": 99810
+ }
+ },
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661": {
+ "tb1qgdp7aa38x3p2kpn2s5486mkvvx2sktnmxkf47e": {
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce:0": 403476
+ }
+ },
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69": {
+ "tb1qd7tjvgttaxzkszzh5ty4yq97r8wscgteejustc": {
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b:0": 160000,
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c:0": 110000
+ },
+ "tb1qfu50fqxhfkl69n6urjzuhc5ndr96tuaxrh3an8": {
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f:1": 135000
+ }
+ },
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535": {
+ "tb1quhk94rhlsflc4wgxl9qzd6p6wszt30uxt4a0yj": {
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba:1": 8597295
+ }
+ },
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe": {
+ "tb1qjpgepu2p6gyff9a92n2mwst4j2wjktra956lcg": {
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661:1": 302700
+ }
+ },
+ "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b": {
+ "tb1qgcgk7j9kpt2mygmhmnu4zep79cd289t6aely7z": {
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269:0": 99811
+ },
+ "tb1qwu2d7rjn9yxalg7cjw6phqzk77lf3fmsta8rhu": {
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f:0": 3099663
+ }
+ },
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c": {
+ "tb1qn7d2x7272lznt5hhk9s07q3cqnrqljnwa55w6c": {
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0:0": 10000
+ }
+ },
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38": {
+ "tb1qplsf242vay6vavy4eguef855fx3klmp9505g88": {
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d:1": 500000
+ }
+ },
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1": {
+ "tb1qdgscjnjm6w59chq0xgghwtq42vfhhn0murqx6hrfz2yaf2yx9v9skh03as": {
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69:1": 404711
+ }
+ }
+ },
+ "txo": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c": {
+ "tb1qq2tmmcngng78nllq2pvrkchcdukemtj5s6l0zu": {
+ "0": [
+ 10000000,
+ false
+ ]
+ }
+ },
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d": {
+ "tb1qplsf242vay6vavy4eguef855fx3klmp9505g88": {
+ "1": [
+ 500000,
+ false
+ ]
+ }
+ },
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3": {
+ "tb1qjm4rr97lxawx5csc3nu3rxhwnpxqe3s4uhwagu": {
+ "1": [
+ 9598000,
+ false
+ ]
+ }
+ },
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39": {
+ "tb1q07ulrxeuu45uqen0clqe85v5en6rf77cxgxsj5": {
+ "1": [
+ 9799000,
+ false
+ ]
+ }
+ },
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269": {
+ "tb1qgcgk7j9kpt2mygmhmnu4zep79cd289t6aely7z": {
+ "0": [
+ 99811,
+ false
+ ]
+ }
+ },
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f": {
+ "tb1qfu50fqxhfkl69n6urjzuhc5ndr96tuaxrh3an8": {
+ "1": [
+ 135000,
+ false
+ ]
+ }
+ },
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea": {
+ "tb1qusm48zmlzwr32csxdw4ar7atw260h22c8zq7jk": {
+ "1": [
+ 100000,
+ false
+ ]
+ }
+ },
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0": {
+ "tb1q008n3k9xjpcuyx4mlczn9jm2at90ts55yrtynq": {
+ "1": [
+ 3194603,
+ false
+ ]
+ },
+ "tb1qn7d2x7272lznt5hhk9s07q3cqnrqljnwa55w6c": {
+ "0": [
+ 10000,
+ false
+ ]
+ }
+ },
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c": {
+ "tb1qm7ckcjsed98zhvhv3dr56a22w3fehlkxyh4wgd": {
+ "0": [
+ 14695224,
+ false
+ ]
+ }
+ },
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c": {
+ "tb1qvsvr06h2phnwmzvwxrlkuzfu2ekhjpfpvguyct": {
+ "1": [
+ 6394885,
+ false
+ ]
+ }
+ },
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e": {
+ "tb1q25arh97ze37n6nk74n3js8ls8z7sva3f0d8pnl": {
+ "1": [
+ 3204744,
+ false
+ ]
+ }
+ },
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2": {
+ "tb1qrdzfu6mlgrxpupd4syxrv77ncku89a0y0vd7f3": {
+ "1": [
+ 654165,
+ false
+ ]
+ }
+ },
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e": {
+ "tb1qcmq7v2zg0jjy5g47k90fqd0h7a4mcyp7f3ly6r": {
+ "0": [
+ 302364,
+ false
+ ]
+ }
+ },
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce": {
+ "tb1qgdp7aa38x3p2kpn2s5486mkvvx2sktnmxkf47e": {
+ "0": [
+ 403476,
+ false
+ ]
+ }
+ },
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6": {
+ "tb1q0quewquwhlfgahhsdg0q3r5lmyzufrtp3fzme4": {
+ "1": [
+ 6595885,
+ false
+ ]
+ }
+ },
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba": {
+ "tb1quhk94rhlsflc4wgxl9qzd6p6wszt30uxt4a0yj": {
+ "1": [
+ 8597295,
+ false
+ ]
+ }
+ },
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4": {
+ "tb1qahrz50yej9v7574q9are3urwyqsdcdddmjl9a6": {
+ "1": [
+ 94500,
+ false
+ ]
+ }
+ },
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661": {
+ "tb1qjpgepu2p6gyff9a92n2mwst4j2wjktra956lcg": {
+ "1": [
+ 302700,
+ false
+ ]
+ }
+ },
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b": {
+ "tb1qd7tjvgttaxzkszzh5ty4yq97r8wscgteejustc": {
+ "0": [
+ 160000,
+ false
+ ]
+ }
+ },
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69": {
+ "tb1qdgscjnjm6w59chq0xgghwtq42vfhhn0murqx6hrfz2yaf2yx9v9skh03as": {
+ "1": [
+ 404711,
+ false
+ ]
+ }
+ },
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535": {
+ "tb1qdy4xwmgklqmyrfj336g4f54582zxtm2yhlge8l": {
+ "1": [
+ 7596590,
+ false
+ ]
+ }
+ },
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe": {
+ "tb1qfsxllwl2edccpar9jas9wsxd4vhcewlxqwmn0w27kurkme3jvkdqn4msdp": {
+ "1": [
+ 302547,
+ false
+ ]
+ }
+ },
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c": {
+ "tb1qcwytrrw3wugydlktsh6yvshlk7jwld38akp8l3": {
+ "1": [
+ 8798,
+ false
+ ]
+ }
+ },
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f": {
+ "tb1qwu2d7rjn9yxalg7cjw6phqzk77lf3fmsta8rhu": {
+ "0": [
+ 3099663,
+ false
+ ]
+ }
+ },
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38": {
+ "tb1q8m8pzk9gpjamgrw3y6y8xtfmw754nedldje5q5": {
+ "1": [
+ 99810,
+ false
+ ]
+ },
+ "tb1qssypacgyt40r8t95myqgrhrdhq93f5y4jmgmqe53w4tlydphcnaqmpm8kr": {
+ "2": [
+ 400000,
+ false
+ ]
+ }
+ },
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1": {
+ "tb1qak6t2hcl3se6epvhlffaprvfjuf37xunnxq7c9": {
+ "0": [
+ 404573,
+ false
+ ]
+ }
+ },
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c": {
+ "tb1qd7tjvgttaxzkszzh5ty4yq97r8wscgteejustc": {
+ "0": [
+ 110000,
+ false
+ ]
+ }
+ }
+ },
+ "use_change": true,
+ "use_encryption": false,
+ "verified_tx3": {
+ "024464eae9858f079d2f49bc3db28f92237c63fe950c8ddc6c9b18d1855fa32c": [
+ 1346870,
+ 1530271747,
+ 11,
+ "0000000000000be4817e499bebdcebd9c792f68551bfedbf14fa058fd8db4e2d"
+ ],
+ "03bec7a53ce7c856046f306738a14a976acff34f12a1e3f61f31fa4284486f7d": [
+ 2579582,
+ 1708966959,
+ 675,
+ "0000000000002b0be5f214bcdcd765f6e4cefa1226a6d3a4e1b482da9707603a"
+ ],
+ "03eb4932abf231999f5a698a28f70cfe6b1a2975be0310e348a9898434efb8d3": [
+ 1346978,
+ 1530282040,
+ 3,
+ "00000000000004f73116c1b9d17dafc409710362110c7cd9e52f1e8703147238"
+ ],
+ "0ef1c91636d20331acc2759d0f0335925cd899e270efdd2598e9449c66649d39": [
+ 1346957,
+ 1530280346,
+ 10,
+ "00000000000004e05e5f82a8f3db5f37cf8b3c8ad4d44a80556c63173f2fdb8d"
+ ],
+ "10a2a7695a5b6ba857b10d04ad047b968656c6d84b58ea226dc92c150cb73b2f": [
+ 1892447,
+ 1605658960,
+ 42,
+ "000000000000009e8f7f68acdbf0d5d20d920794916249d7300d3d4ddea01a7a"
+ ],
+ "2042e1e197e0b21e83de0ee8a92abf5e1fea617cf143dc584a743df075bc6269": [
+ 1583044,
+ 1571148168,
+ 273,
+ "00000000691cd68ef23f24f9bd245aaf85f8dec4abe26d1f9ff97681af9eb222"
+ ],
+ "23b5490a6b9723a96f8d1170cf0ab8d2e961d775cff0151540400f2964ccbd7f": [
+ 2404107,
+ 1667575669,
+ 18,
+ "00000000000000dd5c68b0c41dcd77382c18fada64632c0475b906f0f6a936d7"
+ ],
+ "25ddebe4b7e7caf7b175762ddc4cbb5d704f27520a997b7672057f2455b2fcea": [
+ 1775825,
+ 1594136749,
+ 81,
+ "000000007583aa1310c8368737317e3443990a03c2bdcc95513f82c21e7e8609"
+ ],
+ "30bc3855a6837562eb6dffbb3190e71279e2df2a04f2598780c9df7f290706e0": [
+ 1455208,
+ 1549129020,
+ 23,
+ "000000000000002bbb81515fa47a4aaa4af7b324b606df83b90be6018172ba99"
+ ],
+ "3407144b10e0c7f60a88e9f3adcc7e732c83e92fa17a923f523b8acc50718c2f": [
+ 1568264,
+ 1562691216,
+ 85,
+ "000000000000027001cae7dc4b5b14a66f2219d13a620e1ed12bf5a7a9f444fd"
+ ],
+ "4029e471c030066ecca20621cc2917cade0fd44c5cbcb6f5299c086f25ec4b6c": [
+ 1455781,
+ 1549379178,
+ 61,
+ "000000000000006f773a59309e17ce987f302945141d657b6e310bf04a5a11d2"
+ ],
+ "41284afa51eccc94d10fac25eaa59d1fa1afdc3e27bae2b16a64399fe0f1be3c": [
+ 1414562,
+ 1538150440,
+ 81,
+ "0000000085ee7b5accad0063fc1a368d905cbf704223bccac1b9d2e351a4578d"
+ ],
+ "4dbdf2810f130653d1eb17ce3645d0362e4ef41ffbd521aad1f9294c95baf76e": [
+ 1454548,
+ 1548893536,
+ 44,
+ "000000000000004bfd6914c088814a418c4308cc0f6b87b993455b1a2eca7531"
+ ],
+ "59a9ff5fa62586f102b92504584f52e47f4ca0d5af061e99a0a3023fa70a70e2": [
+ 2579584,
+ 1708968409,
+ 1814,
+ "00000000000000120fe076771544efb54ec2ed49c0ad1278bb49f9c9efa51944"
+ ],
+ "66f846809c2d45842f17e40fb6a696288c3274a66b0bf71107940f250978832e": [
+ 2528392,
+ 1696346138,
+ 155,
+ "00000000f7d4a4dcb86d377009f654832d52f9f94299c51434eab00038e4ab6a"
+ ],
+ "6ff55c62470cf51cbc577e8efac5028e06e41912a39356ba576ab7b4d7f708ce": [
+ 2415285,
+ 1673173967,
+ 17,
+ "00000000000000105c5d1016f327809add5b052aa5032e34cab26050535143ff"
+ ],
+ "737857196825fa2e600bac822c866f10e5f484b0f193cff1fc3d38b91884c7a6": [
+ 1414311,
+ 1537898497,
+ 45,
+ "0000000000000046df1be1854387a550e40df1f487218b6b977b7323db986c45"
+ ],
+ "7726939a1246c0b99c2f30cfa21504af5b458755231b2cdc1fa1e9af2d9d70ba": [
+ 1414310,
+ 1537897872,
+ 48,
+ "0000000050c14b6289103b0a666e76bb178e757f0411947e9461ae82483df756"
+ ],
+ "778b01899d5ed48df03e406bc5babd1fdc8f1be4b7e5b9d20dd8caf24dd66ff4": [
+ 2579584,
+ 1708968409,
+ 284,
+ "00000000000000120fe076771544efb54ec2ed49c0ad1278bb49f9c9efa51944"
+ ],
+ "87dbf5a7a089f1e071559cdfd3e4bddb841cdd0032ffeec328952abf76fe3661": [
+ 2418761,
+ 1675433925,
+ 56,
+ "00000000000037c0576afb47f7f757544ad44710a08c7829dcfc56710d2216d8"
+ ],
+ "8c09f8984e231fcaebdda00b2597cfde3d825a26dad2c5478c78ccabbac1f28b": [
+ 2349377,
+ 1664914506,
+ 58,
+ "000000003309f969ede680d3aea4ca8643912b4b4b793172de15a127a790f778"
+ ],
+ "94fce9de7e82741cc982936ad9f7900372b80be8100cb80e49f5d78c2954fb69": [
+ 2406297,
+ 1668461654,
+ 24,
+ "000000000000001c4dd10ac70eb8f0af901aec27be9469354f98f15beeebdc03"
+ ],
+ "972d16040c71b6660b52a0ad06ddb17aa7097921643bab526ad2e00780004535": [
+ 1414311,
+ 1537898497,
+ 44,
+ "0000000000000046df1be1854387a550e40df1f487218b6b977b7323db986c45"
+ ],
+ "97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe": [
+ 2425096,
+ 1679252503,
+ 21,
+ "000000000000001221ae1721de7f87c9313912720a43b867546e1c93f5d94945"
+ ],
+ "b234c4f252cd193377bc6cf931ff1ef6a398e3a434e43b22cdb457a8213d749b": [
+ 1583044,
+ 1571148168,
+ 286,
+ "00000000691cd68ef23f24f9bd245aaf85f8dec4abe26d1f9ff97681af9eb222"
+ ],
+ "b424362ec4eb39eb84f0bea8e00ffd8382d5dde29092bd1e268b9a027dc5225c": [
+ 1457134,
+ 1550165277,
+ 52,
+ "0000000000000057c116d32f1edf128ec4f81a5e734a4095cda3c9ef1d6cb7f7"
+ ],
+ "ba48bce5cde8a4eacb4f1273c462101c06305e82d6ee89a113cd6b068178651f": [
+ 1583044,
+ 1571148168,
+ 191,
+ "00000000691cd68ef23f24f9bd245aaf85f8dec4abe26d1f9ff97681af9eb222"
+ ],
+ "c96b3ed60c8531e1370051662390defa07152cbc028855ea591fc444d8db4e38": [
+ 2579583,
+ 1708967515,
+ 479,
+ "000000000000001f81c50f287422a15099c2bc5cd4464d9962f84e8c1a7b94bb"
+ ],
+ "d57bb7a379c7eb8c86e34d595722ca4f389394b4497c1f0028971e0a8e75c8e1": [
+ 2415285,
+ 1673173967,
+ 16,
+ "00000000000000105c5d1016f327809add5b052aa5032e34cab26050535143ff"
+ ],
+ "efca2a86e0584d7e359e5bc9ac954c6ed7e5c136ad6d561cba662001221df23c": [
+ 2349374,
+ 1664911683,
+ 13,
+ "0000000000000012fd19ff17ac6890f0911be5791042eb2a6243e3a6b4817e94"
+ ]
+ },
+ "wallet_nonce": 129,
+ "wallet_type": "standard",
+ "winpos-qt": [
+ 344,
+ 374,
+ 1336,
+ 899
+ ]
+}
\ No newline at end of file