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: +
    +
  1. + An Alert Transaction which sends the funds from the wallet to itself (consolidating the UTXOs). +
  2. +
  3. + 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). +
  4. +
+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: +
    +
  1. + Due to the way Bitcoin transactions and UTXOs work, spending funds from the wallet might break + the entire Timelock Recovery plan. +
  2. +
  3. + Adding more funds to the wallet will not be covered by the Timelock Recovery plan. +
  4. +
+
+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: +
    +
  1. + Please prepare in advance the addresses of your inheritors/backup-wallets. +
  2. +
  3. + 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. +
  4. +
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