diff --git a/bal.py b/bal.py
index b5e415f..e516c00 100644
--- a/bal.py
+++ b/bal.py
@@ -1,14 +1,13 @@
import os
-
+from datetime import date, datetime, timedelta
+import platform
# import random
# import zipfile as zipfile_lib
-
-from electrum import json_db
+from electrum import constants, json_db
from electrum.logging import get_logger
from electrum.plugin import BasePlugin
from electrum.transaction import tx_from_any
-
_logger = get_logger(__name__)
def get_will_settings(x):
# print(x)
@@ -50,6 +49,12 @@ class BalConfig:
class BalPlugin(BasePlugin):
_version=None
__version__ = "0.2.8" #AUTOMATICALLY GENERATED DO NOT EDIT
+ default_app={
+ "Linux":"xdg-open",
+ "Window":"start",
+ "Darwin":"open"
+ }
+ chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin"
def version(self):
if not self._version:
try:
@@ -60,7 +65,7 @@ class BalPlugin(BasePlugin):
except Exception as e:
_logger.error(f"failed to get version: {e}")
self._version="unknown"
- return self._version
+ return self._version
SIZE = (159, 97)
@@ -101,6 +106,7 @@ class BalPlugin(BasePlugin):
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
+ self.WELIST_SERVER = BalConfig(config,"bal_welist_server","https://welist.bitcoin-after.life/")
self.WILLEXECUTORS = BalConfig(
config,
"bal_willexecutors",
@@ -123,18 +129,33 @@ class BalPlugin(BasePlugin):
"selected": True,
}
},
+ "testnet4": {
+ "https://we.bitcoin-after.life": {
+ "base_fee": 100000,
+ "status": "New",
+ "info": "Bitcoin After Life Will Executor",
+ "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
+ "selected": True,
+ }
+ },
+ "regtest": {
+ "https://we.bitcoin-after.life": {
+ "base_fee": 100000,
+ "status": "New",
+ "info": "Bitcoin After Life Will Executor",
+ "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
+ "selected": True,
+ }
+ },
},
)
self.WILL_SETTINGS = BalConfig(
config,
"bal_will_settings",
- {
- "baltx_fees": 100,
- "threshold": "180d",
- "locktime": "1y",
- },
+ BalPlugin.default_will_settings(),
)
-
+ self.system = platform.system()
+ self.CALENDAR_APP = BalConfig(config,"bal_open_app",self.default_app[self.system])
self._hide_invalidated = self.HIDE_INVALIDATED.get()
self._hide_replaced = self.HIDE_REPLACED.get()
@@ -150,15 +171,22 @@ class BalPlugin(BasePlugin):
self.HIDE_REPLACED.set(self._hide_replaced)
def validate_will_settings(self, will_settings):
+ defaults=BalPlugin.default_will_settings()
if not will_settings:
will_settings=[]
- if int(will_settings.get("baltx_fees", 1)) < 1:
- will_settings["baltx_fees"] = 1
+ if int(will_settings.get("baltx_fees", 0)) < 1:
+ will_settings["baltx_fees"] = defaults['baltx_fees']
if not will_settings.get("threshold"):
- will_settings["threshold"] = "180d"
+ will_settings["threshold"] = defaults['threshold']
if not will_settings.get("locktime"):
- will_settings["locktime"] = "1y"
+ will_settings["locktime"] = defaults['locktime']
return will_settings
- def default_will_settings(self):
- return {"baltx_fees": 100, "threshold": "180d", "locktime": "1y"}
+ @staticmethod
+ def default_will_settings():
+ today = date.today()
+ dt = datetime(today.year, today.month, today.day, 0, 0, 0)
+ threshold =(dt + timedelta(days=180)).timestamp()
+ locktime =(dt + timedelta(days=365)).timestamp()
+
+ return {"baltx_fees": 100, "threshold": threshold, "locktime": locktime}
diff --git a/heirs.py b/heirs.py
index af6c7e8..b9021c3 100644
--- a/heirs.py
+++ b/heirs.py
@@ -34,6 +34,7 @@ from electrum.transaction import (
# TxOutput,
)
from electrum.util import (
+ BitcoinException,
bfh,
read_json_file,
to_string,
@@ -43,7 +44,7 @@ from electrum.util import (
from .util import Util
from .willexecutors import Willexecutors
-from electrum.util import BitcoinException
+
if TYPE_CHECKING:
from .simple_config import SimpleConfig
@@ -73,10 +74,10 @@ def reduce_outputs(in_amount, out_amount, fee, outputs):
def create_op_return_script(data_hex: str) -> bytes:
"""Crea scriptpubkey OP_RETURN in bytes"""
data = bytes.fromhex(data_hex)
-
+
if len(data) > 80:
raise ValueError("OP_RETURN data too big (max 80 bytes)")
-
+
# Costruzione manuale: OP_RETURN + push data
if len(data) <= 75:
# Formato più comune: OP_RETURN + 1-byte length + data
@@ -84,7 +85,7 @@ def create_op_return_script(data_hex: str) -> bytes:
else:
# Per dati più grandi (fino a 80) si usa OP_PUSHDATA1
script = b'\x6a\x4c' + bytes([len(data)]) + data
-
+
return script
def prepare_transactions(locktimes, available_utxos, fees, wallet):
@@ -162,7 +163,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
random.shuffle(outputs)
#op_return_text = "Hello Bal!"
-
+
## Convert text to hex
#op_return_hex = op_return_text.encode('utf-8').hex()
#op_return_script = create_op_return_script(op_return_hex)
diff --git a/qt.py b/qt.py
index 0a31250..59faedf 100644
--- a/qt.py
+++ b/qt.py
@@ -1,95 +1,67 @@
"""
-Bal
+BAL
Bitcoin after life
-
"""
-
+import subprocess
+import os
import copy
import enum
import sys
import time
-from datetime import datetime
+import traceback
+from datetime import datetime,timedelta,timezone
from decimal import Decimal
from functools import partial
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Union
-try:
- QT_VERSION = sys._GUI_QT_VERSION
-except Exception:
- QT_VERSION = 6
-if QT_VERSION == 5:
- from PyQt5.QtCore import (
- QDateTime,
- QModelIndex,
- QPersistentModelIndex,
- Qt,
- pyqtSignal,
- )
- from PyQt5.QtGui import (
- QColor,
- QPainter,
- QPalette,
- QStandardItem,
- QStandardItemModel,
- )
- from PyQt5.QtWidgets import (
- QAbstractItemView,
- QCheckBox,
- QComboBox,
- QDateTimeEdit,
- QGridLayout,
- QHBoxLayout,
- QLabel,
- QLineEdit,
- QMenu,
- QMenuBar,
- QPushButton,
- QScrollArea,
- QSizePolicy,
- QSpinBox,
- QStyle,
- QStyleOptionFrame,
- QVBoxLayout,
- QWidget,
- )
-else: # QT6
- from PyQt6.QtCore import (
- QDateTime,
- QModelIndex,
- QPersistentModelIndex,
- Qt,
- pyqtSignal,
- )
- from PyQt6.QtGui import (
- QColor,
- QPainter,
- QPalette,
- QStandardItem,
- QStandardItemModel,
- )
- from PyQt6.QtWidgets import (
- QAbstractItemView,
- QCheckBox,
- QComboBox,
- QDateTimeEdit,
- QGridLayout,
- QHBoxLayout,
- QLabel,
- QLineEdit,
- QMenu,
- QMenuBar,
- QPushButton,
- QScrollArea,
- QSizePolicy,
- QSpinBox,
- QStyle,
- QStyleOptionFrame,
- QVBoxLayout,
- QWidget,
- )
+from electrum.gui.qt.util import getSaveFileName
+from electrum.util import FileExportFailed
+from typing import Any, Optional, Union
+from PyQt6.QtWidgets import QWidget, QHBoxLayout, QLabel, QComboBox, QLineEdit, QDateTimeEdit
+from PyQt6.QtCore import pyqtSignal, QDateTime, Qt
+from PyQt6.QtGui import QPainter
+#from PyQt6.QtStyle import QStyle, QStyleOptionFrame
+import tempfile
+
+#DELETEME
+
+from PyQt6.QtCore import (
+ QDateTime,
+ QModelIndex,
+ QPersistentModelIndex,
+ Qt,
+ pyqtSignal,
+)
+from PyQt6.QtGui import (
+ QColor,
+ QPainter,
+ QPalette,
+ QStandardItem,
+ QStandardItemModel,
+)
+from PyQt6.QtWidgets import (
+ QAbstractItemView,
+ QCheckBox,
+ QComboBox,
+ QDateTimeEdit,
+ QGridLayout,
+ QHBoxLayout,
+ QLabel,
+ QLineEdit,
+ QMenu,
+ QMenuBar,
+ QPushButton,
+ QScrollArea,
+ QSizePolicy,
+ QSpinBox,
+ QStyle,
+ QStyleOptionFrame,
+ QVBoxLayout,
+ QWidget,
+)
from electrum.bitcoin import (
NLOCKTIME_BLOCKHEIGHT_MAX,
@@ -138,7 +110,7 @@ from electrum.util import (
)
from .bal import BalPlugin
-from .heirs import Heirs, HEIR_REAL_AMOUNT, HEIR_DUST_AMOUNT
+from .heirs import HEIR_DUST_AMOUNT, HEIR_REAL_AMOUNT, Heirs
from .util import Util
from .will import (
AmountException,
@@ -162,13 +134,13 @@ _logger = get_logger(__name__)
class Plugin(BalPlugin, Logger):
def __init__(self, parent, config, name):
Logger.__init__(self)
- self.logger.info("INIT BALPLUGIN")
+ _logger.info("INIT BALPLUGIN")
BalPlugin.__init__(self, parent, config, name)
self.bal_windows = {}
@hook
def init_qt(self, gui_object):
- self.logger.info("HOOK bal init qt")
+ _logger.info("HOOK bal init qt")
try:
self.gui_object = gui_object
for window in gui_object.windows:
@@ -190,18 +162,18 @@ class Plugin(BalPlugin, Logger):
w.init_menubar_tools(menu_child)
except Exception as e:
- self.logger.error(
+ _logger.error(
("init_qt except:", menu_child.text())
)
raise e
except Exception as e:
- self.logger.error("Error loading plugini {}".format(e))
+ _logger.error("Error loading plugini {}".format(e))
raise e
@hook
def create_status_bar(self, sb):
- self.logger.info("HOOK create status bar")
+ _logger.info("HOOK create status bar")
return
b = StatusBarButton(
read_QIcon_from_bytes(self.bal_plugin.read_file("icons/bal32x32.png")),
@@ -213,13 +185,13 @@ class Plugin(BalPlugin, Logger):
@hook
def init_menubar(self, window):
- self.logger.info("HOOK init_menubar")
+ _logger.info("HOOK init_menubar")
w = self.get_window(window)
w.init_menubar_tools(window.tools_menu)
@hook
def load_wallet(self, wallet, main_window):
- self.logger.debug("HOOK load wallet")
+ _logger.debug("HOOK load wallet")
w = self.get_window(main_window)
# havetoupdate = Util.fix_will_settings_tx_fees(wallet.db)
w.wallet = wallet
@@ -232,18 +204,18 @@ class Plugin(BalPlugin, Logger):
@hook
def close_wallet(self, wallet):
- self.logger.debug("HOOK close wallet")
- for winid, win in self.bal_windows.items():
+ _logger.debug("HOOK close wallet")
+ for _winid, win in self.bal_windows.items():
if win.wallet == wallet:
win.on_close()
@hook
def init_keystore(self):
- self.logger.debug("init keystore")
+ _logger.debug("init keystore")
@hook
def daemon_wallet_loaded(self, boh, wallet):
- self.logger.debug("daemon wallet loaded")
+ _logger.debug("daemon wallet loaded")
def get_window(self, window):
w = self.bal_windows.get(window.winId, None)
@@ -304,9 +276,12 @@ class Plugin(BalPlugin, Logger):
heir_hide_invalidated = bal_checkbox(
self.HIDE_INVALIDATED, on_multiverse_change
)
-
heir_repush = QPushButton("Rebroadcast transactions")
heir_repush.clicked.connect(partial(self.broadcast_transactions, True))
+ bal_mode = QComboBox()
+ options =["Easy","Advanced","Experimental"]
+ bal_mode.addItems(options)
+
grid = QGridLayout(d)
add_widget(
grid,
@@ -322,6 +297,10 @@ class Plugin(BalPlugin, Logger):
2,
"Hide invalidated transactions from will detail and list",
)
+ add_widget(grid,"Calendar App", QLineEdit(self.CALENDAR_APP.get()),3,"Default app used to open calendar",)
+
+ add_widget(grid,"Bal Mode",bal_mode,4,"choose bal mode")
+
# add_widget(
# grid,
# "Ping Willexecutors",
@@ -362,11 +341,11 @@ class Plugin(BalPlugin, Logger):
return False
def broadcast_transactions(self, force):
- for k, w in self.bal_windows.items():
+ for _k, w in self.bal_windows.items():
w.broadcast_transactions(force)
def update_all(self):
- for k, w in self.bal_windows.items():
+ for _k, w in self.bal_windows.items():
w.update_all()
def get_window_title(self, title):
@@ -386,9 +365,9 @@ class shown_cv:
self.value = value
-class BalWindow(Logger):
+class BalWindow():
+
def __init__(self, bal_plugin: "BalPlugin", window: "ElectrumWindow"):
- Logger.__init__(self)
self.bal_plugin = bal_plugin
self.window = window
self.heirs = {}
@@ -466,7 +445,7 @@ class BalWindow(Logger):
self.will[wid] = w.to_dict()
def init_will(self):
- self.logger.info("********************init_____will____________**********")
+ _logger.info("********************init_____will____________**********")
if not self.willexecutors:
self.willexecutors = Willexecutors.get_willexecutors(
self.bal_plugin, update=False, bal_window=self
@@ -493,10 +472,10 @@ class BalWindow(Logger):
# self.will_settings = self.wallet.db.get_dict("will_settings")
# Util.fix_will_settings_tx_fees(self.will_settings)
- # self.logger.info("will_settings: {}".format(self.will_settings))
+ # _logger.info("will_settings: {}".format(self.will_settings))
# if not self.will_settings:
# Util.copy(self.will_settings, self.bal_plugin.default_will_settings())
- # self.logger.debug("not_will_settings {}".format(self.will_settings))
+ # _logger.debug("not_will_settings {}".format(self.will_settings))
# self.bal_plugin.validate_will_settings(self.will_settings)
# self.heir_list_widget.update_will_settings()
# self.heir_list_widget.update()
@@ -548,9 +527,9 @@ class BalWindow(Logger):
heir_amount.setText(
str(Util.decode_amount(heir[1], self.window.get_decimal_point()))
)
- self.heir_locktime = HeirsLockTimeEdit(self.window, 0)
+ self.heir_locktime = LockTimeWidget(self.bal_window, self, heir[2])
if heir:
- self.heir_locktime.set_locktime(heir[2])
+ self.heir_locktime.set_value(heir[2])
# heir_is_xpub = QCheckBox()
@@ -594,7 +573,7 @@ class BalWindow(Logger):
heir_name.text(),
heir_address.text(),
Util.encode_amount(heir_amount.text(), self.window.get_decimal_point()),
- str(self.heir_locktime.get_locktime()),
+ str(self.heir_locktime.get_value()),
]
try:
self.set_heir(heir)
@@ -631,7 +610,7 @@ class BalWindow(Logger):
)
def export_heirs(self):
- Util.export_meta_gui(self.window, _("heirs"), self.heirs.export_file)
+ export_meta_gui(self.window, "heirs.json", self.heirs.export_file)
def prepare_will(self, ignore_duplicate=False, keep_original=False):
will = self.build_inheritance_transaction(
@@ -660,7 +639,7 @@ class BalWindow(Logger):
if not self.no_willexecutor:
f = False
- for u, w in self.willexecutors.items():
+ for _u, w in self.willexecutors.items():
if Willexecutors.is_selected(w):
f = True
if not f:
@@ -676,7 +655,7 @@ class BalWindow(Logger):
self.date_to_check,
)
- self.logger.info(f"txs built: {txs}")
+ _logger.info(f"txs built: {txs}")
creation_time = time.time()
if txs:
for txid in txs:
@@ -696,13 +675,13 @@ class BalWindow(Logger):
will[txid] = WillItem(tx, _id=txid, wallet=self.wallet)
self.update_will(will)
else:
- self.logger.info("No transactions was built")
- self.logger.info(f"will-settings: {self.will_settings}")
- self.logger.info(f"date_to_check:{self.date_to_check}")
- self.logger.info(f"heirs: {self.heirs}")
+ _logger.info("No transactions was built")
+ _logger.info(f"will-settings: {self.will_settings}")
+ _logger.info(f"date_to_check:{self.date_to_check}")
+ _logger.info(f"heirs: {self.heirs}")
return {}
except Exception as e:
- self.logger.info(f"Exception build_will: {e}")
+ _logger.info(f"Exception build_will: {e}")
raise e
pass
return self.willitems
@@ -733,34 +712,20 @@ class BalWindow(Logger):
def show_critical(self, text):
self.window.show_critical(text)
- def update_locktime_widgets(self,locktime):
- locktime = self.will_settings["locktime"] = (
- locktime
- if locktime
- else "1y"
- )
+ def update_setting_widgets(self,new_value,field,update_all=False,update_will_dialog=False,update_heirs_dialog=False,):
+ new_value = self.will_settings[field] = new_value if new_value else BalPlugin.default_will_settings()[field]
+ self.will_settings[field]=new_value
self.bal_plugin.WILL_SETTINGS.set(self.will_settings)
- try:
- self.heir_list_widget.heir_locktime.set_locktime(locktime)
- except Exception:
- pass
- #self.preview_list.heirs_locktime.set_locktim(will_settings['thershold'])
-
- def update_threshold_widgets(self,threshold):
- threshold = self.will_settings["threshold"] = (
- threshold
- if threshold
- else "1y"
- )
- self.bal_plugin.WILL_SETTINGS.set(self.will_settings)
- try:
- self.heir_list_widget.heir_threshold.set_locktime(threshold)
- except Exception:
- pass
- try:
- self.will_list.heir_threshold.set_locktime(threshold)
- except Exception:
- pass
+ if update_all or update_will_dialog:
+ try:
+ self.will_list.will_settings_widget.widgets[field].set_value(new_value)
+ except Exception as e:
+ _logger.error(f"error setting will_settings_widgets[{field}].set_value({new_value})")
+ if update_all or update_heirs_dialog:
+ try:
+ self.heir_list_widget.will_settings_widget.widgets[field].set_value(new_value)
+ except Exception as e:
+ _logger.error(f"error updating settings widget {e}")
def init_heirs_to_locktime(self, multiverse=False):
for heir in self.heirs:
@@ -785,19 +750,22 @@ class BalWindow(Logger):
)
if self.date_to_check < datetime.now().timestamp():
raise CheckAliveException(self.date_to_check)
+
self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get())
+
except Exception as e:
- self.logger.error(f"init_class_variables: {e}")
+ _logger.error(f"init_class_variables: {e}")
+
raise e
def build_inheritance_transaction(self, ignore_duplicate=True, keep_original=True):
try:
if self.disable_plugin:
- self.logger.info("plugin is disabled")
+ _logger.info("plugin is disabled")
return
if not self.heirs:
- self.logger.warning("not heirs {}".format(self.heirs))
+ _logger.warning("not heirs {}".format(self.heirs))
return
try:
self.init_class_variables()
@@ -823,7 +791,7 @@ class BalWindow(Logger):
return
if not self.no_willexecutor:
f = False
- for k, we in self.willexecutors.items():
+ for _k, we in self.willexecutors.items():
if Willexecutors.is_selected(we):
f = True
if not f:
@@ -840,7 +808,7 @@ class BalWindow(Logger):
except NoHeirsException:
return
except NotCompleteWillException as e:
- self.logger.info("{}:{}".format(type(e), e))
+ _logger.info("{}:{}".format(type(e), e))
message = False
if isinstance(e, HeirChangeException):
message = "Heirs changed:"
@@ -858,12 +826,12 @@ class BalWindow(Logger):
f"{_(message)}:\n {e}\n{_('will have to be built')}"
)
- self.logger.info("build will")
+ _logger.info("build will")
self.build_will(ignore_duplicate, keep_original)
try:
self.check_will()
- for wid, w in self.willitems.items():
+ for wid, _w in self.willitems.items():
self.wallet.set_label(wid, "BAL Transaction")
except WillExpiredException as e:
self.invalidate_will()
@@ -903,7 +871,7 @@ class BalWindow(Logger):
read_QIcon_from_bytes(self.bal_plugin.read_file("icons/bal32x32.png"))
)
except SerializationError as e:
- self.logger.error("unable to deserialize the transaction")
+ _logger.error("unable to deserialize the transaction")
parent.show_critical(
_("Electrum was unable to deserialize the transaction:") + "\n" + str(e)
)
@@ -934,8 +902,7 @@ class BalWindow(Logger):
self.show_message(_("No transactions to invalidate"))
def on_failure(exec_info):
- self.show_error(f"ERROR:{exec_info}")
-
+ log_error(exec_info,self.bal_window)
fee_per_byte = self.will_settings.get("baltx_fees", 1)
task = partial(Will.invalidate_will, self.willitems, self.wallet, fee_per_byte)
msg = _("Calculating Transactions")
@@ -1037,9 +1004,8 @@ class BalWindow(Logger):
except Exception as e:
raise e
- def on_failure(exc_info):
- self.logger.info("sign fail: {}".format(exc_info))
- self.show_error(exc_info)
+ def on_failure(exec_info):
+ log_error(exec_info,self.bal_window)
password = self.get_wallet_password()
task = partial(self.sign_transactions, password)
@@ -1053,27 +1019,28 @@ class BalWindow(Logger):
def on_success(sulcess):
self.will_list.update()
if sulcess:
- self.logger.info("error, some transaction was not sent")
+ _logger.info("error, some transaction was not sent")
self.show_warning(_("Some transaction was not broadcasted"))
return
- self.logger.debug("OK, sulcess transaction was sent")
+ _logger.debug("OK, sulcess transaction was sent")
self.show_message(
_("All transactions are broadcasted to respective Will-Executors")
)
- def on_failure(err):
- a,b,c = err
- self.logger.error(f"fail to broadcast transactions:{err}")
- self.logger.error(f"error: {b}")
- self.logger.error("traceback ")
- tb = c
- while tb is not None:
- frame = tb.tb_frame
- self.logger.error("file:", frame.f_code.co_filename)
- self.logger.error("name:", frame.f_code.co_name)
- self.logger.error("line:", tb.tb_lineno)
- self.logger.error("lasti:", tb.tb_lasti)
- tb = tb.tb_next
+ def on_failure(exec_info):
+ log_error(exec_info,self.bal_window)
+ #a,b,c = err
+ #_logger.error(f"fail to broadcast transactions:{err}")
+ #_logger.error(f"error: {b}")
+ #_logger.error("traceback ")
+ #tb = c
+ #while tb is not None:
+ # frame = tb.tb_frame
+ # _logger.error("file:", frame.f_code.co_filename)
+ # _logger.error("name:", frame.f_code.co_name)
+ # _logger.error("line:", tb.tb_lineno)
+ # _logger.error("lasti:", tb.tb_lasti)
+ # tb = tb.tb_next
task = partial(self.push_transactions_to_willexecutors, force)
msg = _("Selecting Will-Executors")
@@ -1119,7 +1086,7 @@ class BalWindow(Logger):
w = self.willitems[wid]
w.set_check_willexecutor(
Willexecutors.check_transaction(
- wid,
+ wid,
w.we["url"]
)
)
@@ -1142,7 +1109,7 @@ class BalWindow(Logger):
def export_will(self):
try:
- Util.export_meta_gui(self.window, _("will.json"), self.export_json_file)
+ export_meta_gui(self.window, "will.json", self.export_json_file)
except Exception as e:
self.show_error(str(e))
raise e
@@ -1184,9 +1151,10 @@ class BalWindow(Logger):
self.update_all()
pass
- def on_failure(e):
- self.logger.error(f"error checking transactions {e}")
- pass
+ def on_failure(exec_info):
+ log_error(exec_info,self.bal_window)
+ #_logger.error(f"error checking transactions {e}")
+ #pass
task = partial(self.check_transactions_task, will)
msg = _("Check Transaction")
@@ -1195,8 +1163,33 @@ class BalWindow(Logger):
)
self.waiting_dialog.exe()
+ def update_willexecutor_list_widget(self,parent,willexecutors):
+ try:
+ parent.willexecutors_list.update(willexecutors)
+ parent.will_executor_list_widget.update()
+ except Exception as e:
+ _logger.error(f"impossible to update will_executor_list_widget {e}")
+ self.will_executors.update()
+
+ def download_list(self,willexecutors,fn_on_success,fn_on_failure=None):
+
+ def on_success(result):
+ #self.willexecutors.update(result)
+ fn_on_success(result)
+ def on_failure(exec_info):
+ fn_on_failure(exec_info)
+
+ if not fn_on_failure:
+ fn_on_success=log_error
+ welist_server = self.bal_plugin.WELIST_SERVER.get()
+ task = partial(Willexecutors.download_list,willexecutors,welist_server)
+ msg = _(f"Downloadinf willexecutors list from {welist_server}")
+ self.waiting_dialog = BalWaitingDialog(
+ self, msg, task, on_success, on_failure, exe=False
+ )
+ self.waiting_dialog.exe()
def ping_willexecutors_task(self, wes):
- self.logger.info("ping willexecutots task")
+ _logger.info("ping willexecutots task")
pinged = []
failed = []
@@ -1227,26 +1220,15 @@ class BalWindow(Logger):
else:
pinged.append(url)
- def ping_willexecutors(self, wes, parent=None):
- if not parent:
- parent = self
-
+ def ping_willexecutors(self, wes, fn_on_success,fn_on_failure=None):
def on_success(result):
- # del self.waiting_dialog
- try:
- parent.will_executor_list_widget.update()
- except Exception:
- pass
- try:
- parent.willexecutors_list.update()
- except Exception:
- pass
+ fn_on_success(result)
- def on_failure(e):
- self.logger.error(f"fail to ping willexecutors: {e}")
- pass
-
- self.logger.info("ping willexecutors")
+ def on_failure(exec_info):
+ fn_on_failure(exec_info)
+ if not fn_on_failure:
+ fn_on_failure=log_error
+ _logger.info("ping willexecutors")
task = partial(self.ping_willexecutors_task, wes)
msg = _("Ping Will-Executors")
self.waiting_dialog = BalWaitingDialog(
@@ -1281,16 +1263,49 @@ def add_widget(grid, label, widget, row, help_):
grid.addWidget(HelpButton(help_), row, 2)
+
+class BalTxFeesWidget(QWidget):
+ valueChanged = pyqtSignal()
+ def __init__(self,bal_window,parent,value=None):
+ super().__init__(parent)
+ self.bal_window=bal_window
+ layout = QHBoxLayout(self)
+ self.txfee_widget=QSpinBox(self)
+ self.txfee_widget.setMinimum(1)
+ self.txfee_widget.setMaximum(10000)
+ value = value if value else self.bal_window.will_settings['baltx_fees']
+ self.txfee_widget.setValue(value)
+ self.txfee_widget.valueChanged.connect(self.on_heir_tx_fees)
+ layout.addWidget(QLabel("Tx Fees:"))
+ layout.addWidget(self.txfee_widget)
+
+ def get_value(self):
+ return self.txfee_widget.value()
+
+ def set_value(self,value):
+ if not value:
+ value=self.bal_window.bal_plugin.default_will_settings()['baltx_fees']
+ return self.txfee_widget.setValue(value)
+
+ def on_heir_tx_fees(self,update_all=False):
+ try:
+ if update_all:
+ self.bal_window.update_setting_widgets(self.get_value(),'baltx_fees',True)
+ except Exception as e:
+ _logger.error("error while trying to update txfees",e )
+
+
class _LockTimeEditor:
min_allowed_value = NLOCKTIME_MIN
max_allowed_value = NLOCKTIME_MAX
- def get_locktime(self) -> Optional[int]:
+ def get_value(self) -> Optional[int]:
raise NotImplementedError()
- def set_locktime(self, x: Any, force=True) -> None:
+ def set_value(self, x: Any, force=True) -> None:
raise NotImplementedError()
+ @classmethod
def is_acceptable_locktime(cls, x: Any) -> bool:
if not x: # e.g. empty string
return True
@@ -1299,21 +1314,38 @@ class _LockTimeEditor:
except Exception:
return False
return cls.min_allowed_value <= x <= cls.max_allowed_value
+ @staticmethod
+ def get_max_allowed_timestamp() -> int:
+ ts = NLOCKTIME_MAX
+ # Test if this value is within the valid timestamp limits (which is platform-dependent).
+ # see #6170
+ try:
+ datetime.fromtimestamp(ts)
+ except (OSError, OverflowError):
+ ts = 2**31 - 1 # INT32_MAX
+ datetime.fromtimestamp(ts) # test if raises
+ return ts
-
-class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
+class BalTimeEditWidget(QWidget, _LockTimeEditor):
valueEdited = pyqtSignal()
- locktime_threshold = 50000000
+ _setting_locktime = False
+ _current_value = None
- def __init__(self, parent=None, default_index=1):
- QWidget.__init__(self, parent)
+ help_text = "if you choose Raw, you can insert various options based on suffix:\n" \
+ + " - d: number of days after current day(ex: 1d means tomorrow)\n" \
+ + " - y: number of years after currrent day(ex: 1y means one year from today)\n"
+ label_text ="locktime"
+
+ def __init__(self, bal_window, parent, default_locktime=None):
+ super().__init__(parent)
+ self.bal_window=bal_window
hbox = QHBoxLayout()
self.setLayout(hbox)
hbox.setContentsMargins(0, 0, 0, 0)
hbox.setSpacing(0)
-
- self.locktime_raw_e = LockTimeRawEdit(self, time_edit=self)
+ self.setMinimumWidth(50*char_width_in_lineedit())
+ self.locktime_raw_e = TimeRawEditWidget(self, time_edit=self)
self.locktime_date_e = LockTimeDateEdit(self, time_edit=self)
self.editors = [self.locktime_raw_e, self.locktime_date_e]
@@ -1324,65 +1356,113 @@ class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
1: self.locktime_date_e,
}
self.combo.addItems(options)
-
- self.editor = self.option_index_to_editor_map[default_index]
+ default_index = 0
+ if not default_locktime:
+ default_locktime = self.bal_window.will_settings[self.base_field]
+ try:
+ int(default_locktime)
+ default_index=1
+ except Exception as e:
+ default_index=0
+ hbox.addWidget(QLabel(self.label_text))
+ hbox.addWidget(HelpButton(self.help_text))
self.combo.currentIndexChanged.connect(self.on_current_index_changed)
+ self.editor = self.option_index_to_editor_map[default_index]
+ self.editor.set_value(default_locktime, force=False)
self.combo.setCurrentIndex(default_index)
self.on_current_index_changed(default_index)
hbox.addWidget(self.combo)
for w in self.editors:
hbox.addWidget(w)
+
hbox.addStretch(1)
# spacer_widget = QWidget()
# spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
# hbox.addWidget(spacer_widget)
-
+ self.valueEdited.connect(lambda: self.update_will_settings(True))
self.locktime_raw_e.editingFinished.connect(self.valueEdited.emit)
self.locktime_date_e.dateTimeChanged.connect(self.valueEdited.emit)
self.combo.currentIndexChanged.connect(self.valueEdited.emit)
- def on_current_index_changed(self, i):
+
+
+ def update_will_settings(self,update_all=False,update_will_dialog=False,update_heirs_dialog=False,):
+ self.bal_window.update_setting_widgets(self.get_value(),self.base_field,update_all,update_will_dialog,update_heirs_dialog)
+
+ def on_current_index_changed(self, i,force=False):
for w in self.editors:
w.setVisible(False)
w.setEnabled(False)
- #prev_locktime = self.editor.get_locktime()
+ #prev_locktime = self.editor.get_value()
self.editor = self.option_index_to_editor_map[i]
#if self.editor.is_acceptable_locktime(prev_locktime):
- # self.editor.set_locktime(prev_locktime, force=False)
+ # self.editor.set_value(prev_locktime, force=False)
self.editor.setVisible(True)
self.editor.setEnabled(True)
- def get_locktime(self) -> Optional[str]:
- return self.editor.get_locktime()
+ def get_value(self) -> Optional[str]:
+ val = self.editor.get_value()
+ return val
- def set_index(self, index):
+ def set_index(self, index,force = False):
self.combo.setCurrentIndex(index)
- self.on_current_index_changed(index)
+ self.on_current_index_changed(index,force)
- def set_locktime(self, x: Any, force=None) -> None:
- if force is None:
- force=True
+ def set_value(self, x: Any, force=None,update_all=False,update_will_dialog=False,update_heirs_dialog=False) -> None:
+ current_val=self.get_value()
+ if self._setting_locktime:
+ return
+ newtime=x
+ current = self.get_value()
+ if x==current:
+ return
+
+ self._setting_locktime = True
try:
- int(x)
- self.set_index(1)
- except Exception:
- if isinstance(x,str):
- self.set_index(0)
- self.editor.set_locktime(x, force=False)
+ if not newtime:
+ newtime=self.bal_window.bal_plugin.WILL_SETTINGS.get()[self.base_field]
+ try:
+ int(x)
+ if force is not None:
+ self.set_index(1,True)
+ except Exception:
+ if isinstance(x,str):
+ if force is not None:
+ self.set_index(0,True)
+ self.current_locktime = newtime
+ self.editor.set_value(newtime, force=False)
+ self.update_will_settings(update_all,update_will_dialog,update_heirs_dialog)
+ finally:
+ self._setting_locktime = False
+
+class TimeRawEditWidget(QWidget):
+ editingFinished = pyqtSignal()
+ def __init__(self,parent,time_edit=None):
+ super().__init__(parent)
+ self.editor=LockTimeRawEdit(parent,time_edit)
+ self.label=QLabel("")
+ self.label.setFixedWidth(17 * char_width_in_lineedit())
+ self.layout=QHBoxLayout(self)
+ self.layout.addWidget(self.editor)
+ self.layout.addWidget(self.label)
+ self.editor.editingFinished.connect(self.editingFinished.emit)
+ self.get_value=self.editor.get_value
+ self.set_value=self.editor.set_value
class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
def __init__(self, parent=None, time_edit=None):
QLineEdit.__init__(self, parent)
- self.setFixedWidth(14 * char_width_in_lineedit())
+ self.setFixedWidth(6 * char_width_in_lineedit())
self.textChanged.connect(self.numbify)
self.isdays = False
self.isyears = False
self.isblocks = False
self.time_edit = time_edit
- def replace_str(self, text):
+ @staticmethod
+ def replace_str(text):
return str(text).replace("d", "").replace("y", "").replace("b", "")
def checkbdy(self, s, pos, appendix):
@@ -1425,72 +1505,33 @@ class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
s = self.replace_str(s) + "y"
if self.isblocks:
s = self.replace_str(s) + "b"
-
- self.set_locktime(s, force=False)
+ self.blockSignals(True)
+ self.setText(s)
+ self.blockSignals(False)
+ #self.set_value(s, force=False)
+ self._current_value = s
# setText sets Modified to False. Instead we want to remember
# if updates were because of user modification.
self.setModified(self.hasFocus())
self.setCursorPosition(pos)
- def get_locktime(self) -> Optional[str]:
+ def get_absolute_value(self):
+ return Util.locktime_to_str(Util.parse_locktime_string(self.get_value()))
+
+ def get_value(self) -> Optional[str]:
try:
return str(self.text())
except Exception:
return None
- def set_locktime(self, x: Any, force=True) -> None:
- out = str(x)
- if "d" in out:
- out = self.replace_str(x) + "d"
- elif "y" in out:
- out = self.replace_str(x) + "y"
- elif "b" in out:
- out = self.replace_str(x) + "b"
- else:
- try:
- out = int(x)
- except Exception:
- self.setText("")
- return
- out = max(out, self.min_allowed_value)
- out = min(out, self.max_allowed_value)
- self.setText(str(out))
-
-
-class LockTimeHeightEdit(LockTimeRawEdit):
- max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX
-
- def __init__(self, parent=None, time_edit=None):
- LockTimeRawEdit.__init__(self, parent)
- self.setFixedWidth(20 * char_width_in_lineedit())
- self.time_edit = time_edit
-
- def paintEvent(self, event):
- super().paintEvent(event)
- panel = QStyleOptionFrame()
- self.initStyleOption(panel)
- textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self)
- textRect.adjust(2, 0, -10, 0)
- painter = QPainter(self)
- painter.setPen(ColorScheme.GRAY.as_color())
- painter.drawText(textRect, int(Qt.AlignRight | Qt.AlignVCenter), "height")
-
-
-def get_max_allowed_timestamp() -> int:
- ts = NLOCKTIME_MAX
- # Test if this value is within the valid timestamp limits (which is platform-dependent).
- # see #6170
- try:
- datetime.fromtimestamp(ts)
- except (OSError, OverflowError):
- ts = 2**31 - 1 # INT32_MAX
- datetime.fromtimestamp(ts) # test if raises
- return ts
+ def set_value(self, x: Any, force=True) -> None:
+ self.setText(str(x))
+ self.numbify()
class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor):
min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1
- max_allowed_value = get_max_allowed_timestamp()
+ max_allowed_value = _LockTimeEditor.get_max_allowed_timestamp()
def __init__(self, parent=None, time_edit=None):
QDateTimeEdit.__init__(self, parent)
@@ -1499,12 +1540,12 @@ class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor):
self.setDateTime(QDateTime.currentDateTime())
self.time_edit = time_edit
- def get_locktime(self) -> Optional[int]:
+ def get_value(self) -> Optional[int]:
dt = self.dateTime().toPyDateTime()
locktime = int(time.mktime(dt.timetuple()))
return locktime
- def set_locktime(self, x: Any, force=False) -> None:
+ def set_value(self, x: Any, force=False) -> None:
if not self.is_acceptable_locktime(x):
self.setDateTime(QDateTime.currentDateTime())
return
@@ -1515,14 +1556,155 @@ class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor):
return
dt = datetime.fromtimestamp(x)
self.setDateTime(dt)
+ self.alarm=dt
+
+class ThresholdTimeWidget(BalTimeEditWidget):
+ help_text = ("Check to ask for invalidation.\n\n"
+ "When less then this time is missing, ask to invalidate.\n"
+ "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n\n"
+ f"{BalTimeEditWidget.help_text}")
+ label_text="Check Alive:"
+ base_field = "threshold"
+
+class LockTimeWidget(BalTimeEditWidget):
+ help_text = ("Set Locktime for transactions.\n"
+ "Any time is needed transaction will be anticipated by 1day\n"
+ f"{BalTimeEditWidget.help_text}")
+ label_text = "Delivery Time:"
+ base_field = "locktime"
+
+class WillSettingsWidget(QWidget):
+ widgets={}
+ def __init__(self,bal_window,parent,layout_type='h'):
+ super().__init__(parent)
+ self.bal_window=bal_window
+ box=QHBoxLayout(self) if layout_type=='h' else QVBoxLayout(self)
+
+ self.calendar_button=QPushButton(_("Calendar"))
+ self.calendar_button.setIcon(read_QIcon_from_bytes(self.bal_window.bal_plugin.read_file("icons/calendar.png")))
+ self.calendar_button.clicked.connect(self.open_or_save_calendar)
+ self.widgets['locktime']=LockTimeWidget(bal_window,self)
+ self.widgets['threshold']= ThresholdTimeWidget(bal_window,self)
+ self.widgets['baltx_fees'] =BalTxFeesWidget(bal_window,self)
+ box.addWidget(self.widgets['locktime'])
+ box.addWidget(self.widgets['threshold'])
+ box.addWidget(self.calendar_button)
+ box.addWidget(self.widgets['baltx_fees'])
+ self.widgets['locktime'].valueEdited.connect(self.on_locktime_change)
+ self.widgets['threshold'].valueEdited.connect(self.on_locktime_change)
+ self.on_locktime_change()
+
+ @staticmethod
+ def write_temp_ics(content):
+ fd, path = tempfile.mkstemp(prefix="event_", suffix=".ics")
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
+ f.write(content)
+ return path
+
+ def open_with_default_app(self,path):
+ try:
+ subprocess.check_call([self.bal_window.bal_plugin.CALENDAR_APP.get(), path])
+ return True
+ except Exception as e:
+ return False
+ @staticmethod
+ def save_to_cwd(path, filename="evento.ics"):
+ target = os.path.abspath(filename)
+ # se il file esiste, sovrascrive
+ with open(path, "rb") as src, open(target, "wb") as dst:
+ dst.write(src.read())
+ return target
+
+ @staticmethod
+ def format_time(time):
+ return time.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
+
+ @staticmethod
+ def ical_escape(text: str) -> str:
+ # escape per RFC5545: backslash, ; , newlines
+ text = text.replace("\\", "\\\\").replace(";", r"\;").replace(",", r"\,").replace("\n", r"\n")
+ return WillSettingsWidget.fold_ical_line(text)
+
+ @staticmethod
+ def fold_ical_line(line: str, limit: int = 75) -> str:
+ # ritorna linee separate da CRLF e folding con spazio iniziale sulle righe successive
+ encoded = line.encode("utf-8")
+ parts = []
+ while len(encoded) > limit:
+ # taglia senza spezzare byte UTF-8
+ cut = limit
+ while (encoded[cut] & 0xC0) == 0x80: # byte di continuazione UTF-8
+ cut -= 1
+ parts.append(encoded[:cut].decode("utf-8"))
+ encoded = encoded[cut:]
+ parts.append(encoded.decode("utf-8"))
+ return "\r\n ".join(parts)
-_NOT_GIVEN = object() # sentinel value
+ def open_or_save_calendar(self):
+ now = self.format_time(datetime.now())
+
+ alarm_start = self.format_time(self.widgets['threshold'].alarm)
+ alarm_end = self.format_time(self.widgets['locktime'].alarm)
+
+ heirs_details = "\n".join(f"{heir} - {self.bal_window.heirs[heir][1]}" for heir in self.bal_window.heirs)
+ event_description = self.ical_escape(f"Your will for wallet {self.bal_window.wallet} is going to expire\n{heirs_details}")
+ uid = f"{int(datetime.now().timestamp())}-{os.getpid()}@local" # UID univoco semplice
+ summary = self.ical_escape(f"Bitcoin After Life {self.bal_window.wallet} expiry")
+ lines = [
+ "BEGIN:VCALENDAR",
+ "VERSION:2.0",
+ "PRODID:-//Example//EN",
+ "BEGIN:VEVENT",
+ f"UID:{uid}",
+ f"DTSTAMP:{now}",
+ f"DTSTART:{alarm_start}",
+ f"DTEND:{alarm_end}",
+ f"SUMMARY:{summary}",
+ f"DESCRIPTION:{event_description}",
+ "END:VEVENT",
+ "END:VCALENDAR",
+ ]
+
+ ics_content = "\r\n".join(lines) + "\r\n"
+ temp_path = self.write_temp_ics(ics_content)
+ opened = self.open_with_default_app(temp_path)
+ if opened:
+ _logger.info(f"File opened with default app: {temp_path}")
+ else:
+ export_meta_gui(self.bal_window.window, f"{self.base_field}_event.csv", self.save_to_cwd)
+
+ def on_locktime_change(self):
+ threshold_value = str(self.widgets['threshold'].get_value())
+ locktime_value = str(self.widgets['locktime'].get_value())
+
+ dt=None
+ if threshold_value[-1] in ['d','y']:
+ ts=int(threshold_value[:-1])
+ min_locktime = min(Will.get_min_locktime(self.bal_window.willitems,NLOCKTIME_MAX),Util.parse_locktime_string(locktime_value))
+ threshold_value = ts*365 if threshold_value[-1] == 'y' else ts
+ dt = datetime.fromtimestamp(min_locktime)
+ dt = dt - timedelta(days = threshold_value)
+ self.widgets['threshold'].editor.label.setText(dt.strftime("%Y-%m-%d"))
+ self.widgets['threshold'].alarm = dt
+ else:
+ self.widgets['threshold'].alarm = datetime.fromtimestamp(Util.parse_locktime_string(self.widgets['threshold'].get_value()))
+
+ if locktime_value[-1] in ['d','y']:
+ lt = int(locktime_value[:-1])
+ locktime_value = lt*365 if locktime_value[-1] == 'y' else lt
+ dt =datetime.now()
+ dt += timedelta(days= locktime_value)
+ dt=dt.replace(hour=0,minute=0,second=0,microsecond=0)
+ self.widgets['locktime'].editor.label.setText(dt.strftime("%Y-%m-%d"))
+ self.widgets['locktime'].alarm = dt
+ else:
+ self.widgets['locktime'].alarm=datetime.fromtimestamp(Util.parse_locktime_string(self.widgets['locktime'].get_value()))
class PercAmountEdit(BTCAmountEdit):
def __init__(
- self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN
+ self, decimal_point, is_int=False, parent=None, *, max_amount=None
):
super().__init__(decimal_point, is_int, parent, max_amount=max_amount)
@@ -1590,9 +1772,18 @@ class PercAmountEdit(BTCAmountEdit):
class BalDialog(WindowModalDialog):
def __init__(self, parent, bal_plugin, title=None, icon="icons/bal32x32.png"):
self.parent = parent
+ self.thread = None
WindowModalDialog.__init__(self, parent, title)
# WindowModalDialog.__init__(self,parent)
self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon)))
+ def closeEvent(self,event):
+ self._stopping=True
+ if self.thread:
+ self.thread.stop()
+ def hideEvent(self,event):
+ self._stopping=True
+ if self.thread:
+ self.thread.stop()
class BalWizardDialog(BalDialog):
@@ -1679,8 +1870,10 @@ class BalWizardDialog(BalDialog):
pass
def closeEvent(self, event):
+ self._stopping = True
+ self.thread.stop()
- self.bal_window.heir_list_widget.update_will_settings()
+ #self.bal_window.heir_list_widget.will_settings_widget.update_will_settings()
pass
@@ -1834,28 +2027,24 @@ class BalWizardWEDownloadWidget(BalWizardWidget):
if index < 2:
def on_success(willexecutors):
- self.bal_window.willexecutors.update(willexecutors)
+ def ping_on_success(result):
+ ping_on_done()
+ def ping_on_failure(exec_info):
+ ping_on_done()
+ def ping_on_done():
+ if index < 1:
+ for we in self.bal_window.willexecutors:
+ if self.bal_window.willexecutors[we]["status"] == 200:
+ self.bal_window.willexecutors[we]["selected"] = True
+ Willexecutors.save(
+ self.bal_window.bal_plugin, self.bal_window.willexecutors
+ )
+
self.bal_window.ping_willexecutors(
- self.bal_window.willexecutors, False
- )
- if index < 1:
- for we in self.bal_window.willexecutors:
- if self.bal_window.willexecutors[we]["status"] == 200:
- self.bal_window.willexecutors[we]["selected"] = True
- Willexecutors.save(
- self.bal_window.bal_plugin, self.bal_window.willexecutors
+ self.bal_window.willexecutors,ping_on_success,ping_on_failure
)
- def on_failure(fail):
- _logger.debug(f"Failed to download willexecutors list {fail}")
- pass
-
- task = partial(Willexecutors.download_list,self.bal_window.willexecutors)
- msg = _("Downloading Will-Executors list")
- self.waiting_dialog = BalWaitingDialog(
- self.bal_window, msg, task, on_success, on_failure, exe=False
- )
- self.waiting_dialog.exe()
+ self.bal_window.download_list(self.bal_window.willexecutors,on_success)
elif index == 3:
# TODO DO NOTHING
@@ -1896,92 +2085,11 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
def get_content(self):
widget = QWidget()
- self.heir_locktime = HeirsLockTimeEdit(widget, 0)
- #will_settings = self.bal_window.bal_plugin.WILL_SETTINGS.get()
- will_settings = self.bal_window.will_settings
- self.heir_locktime.set_locktime(will_settings["locktime"])
-
- def on_heir_locktime():
- if not self.heir_locktime.get_locktime():
- self.heir_locktime.set_locktime("1y")
- self.bal_window.update_locktime_widgets(self.heir_locktime.get_locktime())
-
- self.heir_locktime.valueEdited.connect(on_heir_locktime)
-
- self.heir_threshold = HeirsLockTimeEdit(widget, 0)
- self.heir_threshold.set_locktime(will_settings["threshold"])
-
- def on_heir_threshold():
- if not self.heir_threshold.get_locktime():
- self.heir_threshold.set_locktime("180d")
- self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
-
- self.heir_threshold.valueEdited.connect(on_heir_threshold)
-
- self.heir_tx_fees = QSpinBox(widget)
-
- self.heir_tx_fees.setMinimum(1)
- self.heir_tx_fees.setMaximum(10000)
- self.heir_tx_fees.setValue(will_settings["baltx_fees"])
-
- def on_heir_tx_fees():
- if not self.heir_tx_fees.value():
- self.heir_tx_fees.set_value(1)
- self.bal_window.will_settings["baltx_fees"] = self.heir_tx_fees.value()
- self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
-
- self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
-
- def make_hlayout(label, twidget, help_text):
- tw = QWidget()
- hlayout = QHBoxLayout(tw)
- hlayout.addWidget(QLabel(label))
- hlayout.addWidget(twidget)
- hlayout.addWidget(HelpButton(help_text))
- hlayout.addStretch(1)
- spacer_widget = QWidget()
- spacer_widget.setSizePolicy(
- QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
- )
- hlayout.addWidget(spacer_widget)
- return tw
-
layout = QVBoxLayout(widget)
- layout.addWidget(
- make_hlayout(
- _("Delivery Time:"),
- self.heir_locktime,
- _(
- "Locktime* to be used in the transaction\n"
- + "if you choose Raw, you can insert various options based on suffix:\n"
- + " - d: number of days after current day(ex: 1d means tomorrow)\n"
- + " - y: number of years after currrent day(ex: 1y means one year from today)\n"
- + "* locktime can be anticipated to update will\n"
- ),
- )
- )
- layout.addWidget(
- make_hlayout(
- _("Check Alive:"),
- self.heir_threshold,
- _(
- "Check to ask for invalidation.\n"
- + "When less then this time is missing, ask to invalidate.\n"
- + "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n"
- + "if you choose Raw, you can insert various options based on suffix:\n"
- + " - d: number of days after current day(ex: 1d means tomorrow).\n"
- + " - y: number of years after currrent day(ex: 1y means one year from today).\n\n"
- ),
- )
- )
- layout.addWidget(
- make_hlayout(
- _("Fees(sats/vbyte):"),
- self.heir_tx_fees,
- ("Fee to be used in the transaction"),
- )
- )
+ layout.addWidget(LockTimeWidget(self.bal_window,widget))
+ layout.addWidget(ThresholdTimeWidget(self.bal_window,widget))
+ layout.addWidget(BalTxFeesWidget(self.bal_window,widget))
spacer_widget = QWidget()
spacer_widget.setSizePolicy(
@@ -2053,6 +2161,7 @@ class BalWaitingDialog(BalDialog):
return self.message_label.text()
def closeEvent(self, event):
+ self._stopping = True
self.thread.stop()
@@ -2573,7 +2682,7 @@ class BalBuildWillDialog(BalDialog):
# self.qwidget.adjustSize()
# from PyQt6.QtWidgets import QApplication
# QApplication.processEvents()
- #
+ #
# self.adjustSize()
def msg_update(self):
full_text = "
".join(self.labels).replace("\n", "
")
@@ -2581,7 +2690,7 @@ class BalBuildWillDialog(BalDialog):
self.message_label.adjustSize()
#self.setMinimumHeight(len(self.labels)*40)
self.resize(self.sizeHint())
-
+
def get_text(self):
return self.message_label.text()
@@ -2610,7 +2719,7 @@ class HeirListWidget(MyTreeView, MessageBoxMixin):
def createEditor(self, parent, option, index):
return QLineEdit(parent)
def setEditorData(self, editor, index):
- editor.setText(index.data())
+ editor.setText(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
@@ -2752,7 +2861,6 @@ class HeirListWidget(MyTreeView, MessageBoxMixin):
# FIXME refresh loses sort order; so set "default" here:
self.filter()
run_hook("update_heirs_tab", self)
- self.update_will_settings()
def refresh_row(self, key, row):
# nothing to update here
@@ -2768,98 +2876,19 @@ class HeirListWidget(MyTreeView, MessageBoxMixin):
menu.addAction(_("Import"), self.bal_window.import_heirs)
menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
- threshold = self.bal_window.will_settings.get('threshold',None) if self.bal_window.will_settings else self.bal_window.window.wallet.db.get_dict("will_settings")['threshold']
- locktime = self.bal_window.will_settings.get('locktime',None) if self.bal_window.will_settings else self.bal_window.window.wallet.db.get_dict("will_settings")['locktime']
+ widget = QWidget()
+ layout = QHBoxLayout(widget)
+ self.will_settings_widget=WillSettingsWidget(self.bal_window,widget)
- self.heir_locktime = HeirsLockTimeEdit(self, 0)
- def on_heir_locktime():
- if not self.heir_locktime.get_locktime():
- self.heir_locktime.set_locktime("1y")
- self.bal_window.update_locktime_widgets(self.heir_locktime.get_locktime())
-
- self.heir_locktime.valueEdited.connect(on_heir_locktime)
-
- self.heir_locktime.set_locktime(locktime)
-
- self.heir_threshold = HeirsLockTimeEdit(self, 0)
-
- def on_heir_threshold():
- if not self.heir_threshold.get_locktime():
- self.heir_threshold.set_locktime("180d")
- self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
-
- self.heir_threshold.valueEdited.connect(on_heir_threshold)
- self.heir_threshold.set_locktime(threshold)
-
- self.heir_tx_fees = QSpinBox()
- self.heir_tx_fees.setMinimum(1)
- self.heir_tx_fees.setMaximum(10000)
-
- def on_heir_tx_fees():
- if not self.heir_tx_fees.value():
- self.heir_tx_fees.set_value(1)
- self.bal_window.will_settings["baltx_fees"] = self.heir_tx_fees.value()
- self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
-
- self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
-
- self.heirs_widget = QWidget()
- layout = QHBoxLayout()
- self.heirs_widget.setLayout(layout)
-
- layout.addWidget(QLabel(_("Delivery Time:")))
- layout.addWidget(self.heir_locktime)
- layout.addWidget(
- HelpButton(
- _(
- "Locktime* to be used in the transaction\n"
- + "if you choose Raw, you can insert various options based on suffix:\n"
- + " - d: number of days after current day(ex: 1d means tomorrow)\n"
- + " - y: number of years after currrent day(ex: 1y means one year from today)\n"
- + "* locktime can be anticipated to update will\n"
- )
- )
- )
-
- layout.addWidget(QLabel(" "))
- layout.addWidget(QLabel(_("Check Alive:")))
- layout.addWidget(self.heir_threshold)
- layout.addWidget(
- HelpButton(
- _(
- "Check to ask for invalidation.\n"
- + "When less then this time is missing, ask to invalidate.\n"
- + "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n"
- + "if you choose Raw, you can insert various options based on suffix:\n"
- + " - d: number of days after current day(ex: 1d means tomorrow).\n"
- + " - y: number of years after currrent day(ex: 1y means one year from today).\n\n"
- )
- )
- )
- layout.addWidget(QLabel(" "))
- layout.addWidget(QLabel(_("Fees:")))
- layout.addWidget(self.heir_tx_fees)
- layout.addWidget(HelpButton(_("Fee to be used in the transaction")))
- layout.addWidget(QLabel("sats/vbyte"))
- layout.addWidget(QLabel(" "))
+ layout.addWidget(self.will_settings_widget)
newHeirButton = QPushButton(_("New Heir"))
newHeirButton.clicked.connect(self.bal_window.new_heir_dialog)
layout.addWidget(newHeirButton)
- toolbar.insertWidget(2, self.heirs_widget)
+ toolbar.insertWidget(2, widget)
return toolbar
- def update_will_settings(self):
- try:
- self.heir_locktime.set_locktime(self.bal_window.will_settings["locktime"])
- self.heir_threshold.set_locktime(self.bal_window.will_settings["threshold"])
- self.heir_tx_fees.setValue(int(self.bal_window.will_settings["baltx_fees"]))
-
- except Exception as e:
- pass
- _logger.debug(f"Exception update_will_settings {e}")
-
def build_transactions(self):
# will = self.bal_window.prepare_will()
self.bal_window.prepare_will()
@@ -3074,21 +3103,6 @@ class PreviewList(MyTreeView):
menu.addAction(_("Check"), self.check)
menu.addAction(_("Invalidate"), self.invalidate_will)
- def make_hlayout(label, twidget, help_text):
- tw = QWidget()
- hlayout = QHBoxLayout(tw)
- hlayout.addWidget(QLabel(label))
- hlayout.addWidget(twidget)
- hlayout.addWidget(HelpButton(help_text))
- hlayout.addStretch(1)
- spacer_widget = QWidget()
- spacer_widget.setSizePolicy(
- QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
- )
- hlayout.addWidget(spacer_widget)
- return tw
-
-
wizard = QPushButton(_("Setup Wizard"))
wizard.clicked.connect(self.bal_window.init_wizard)
#display = QPushButton(_("Display"))
@@ -3099,32 +3113,9 @@ class PreviewList(MyTreeView):
widget = QWidget()
hlayout = QHBoxLayout(widget)
- hlayout.addWidget(QLabel(_("Check Alive:")))
+ self.will_settings_widget=WillSettingsWidget(self.bal_window,widget)
- threshold = self.bal_window.will_settings.get('threshold',None) if self.bal_window.will_settings else self.bal_window.window.wallet.db.get_dict("will_settings")['threshold']
-
- self.heir_threshold = HeirsLockTimeEdit(widget, 0)
- self.heir_threshold.set_locktime(threshold)
-
- def on_heir_threshold():
- if not self.heir_threshold.get_locktime():
- self.heir_threshold.set_locktime("180d")
- self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
-
- self.heir_threshold.valueEdited.connect(on_heir_threshold)
- hlayout.addWidget(self.heir_threshold)
- hlayout.addWidget(
- HelpButton(
- _(
- "Check to ask for invalidation.\n"
- + "When less then this time is missing, ask to invalidate.\n"
- + "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n"
- + "if you choose Raw, you can insert various options based on suffix:\n"
- + " - d: number of days after current day(ex: 1d means tomorrow).\n"
- + " - y: number of years after currrent day(ex: 1y means one year from today).\n\n"
- )
- )
- )
+ hlayout.addWidget(self.will_settings_widget)
hlayout.addWidget(wizard)
hlayout.addWidget(refresh)
toolbar.insertWidget(2, widget)
@@ -3598,6 +3589,7 @@ class WillExecutorListWidget(MyTreeView):
self.parent.willexecutors_list[edit_key]["address"] = text
if col == self.Columns.INFO:
self.parent.willexecutors_list[edit_key]["info"] = text
+ self.parent.save_willexecutors()
self.update()
except Exception:
pass
@@ -3684,7 +3676,10 @@ class WillExecutorListWidget(MyTreeView):
set_current = QPersistentModelIndex(idx)
self.set_current_idx(set_current)
self.filter()
- self.parent.save_willexecutors()
+ #try:
+ # self.parent.save_willexecutors()
+ #except Exception as e:
+ # print("exception saving willexecutors",e)
except Exception as e:
_logger.error(f"error updating willexcutor {e}")
@@ -3755,13 +3750,14 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
}
self.will_executor_list_widget.update()
- def download_list(self):
- self.willexecutors_list.update(Willexecutors.download_list(self.bal_window.willexecutors))
- self.will_executor_list_widget.update()
+ def download_list(self,wes=None):
+ if not wes:
+ wes=self.willexecutors_list
+ self.bal_window.download_list(wes,self.save_willexecutors)
def export_file(self, path):
- Util.export_meta_gui(
- self.bal_window.window, _("willexecutors.json"), self.export_json_file
+ export_meta_gui(
+ self.bal_window.window, "willexecutors.json", self.export_json_file
)
def export_json_file(self, path):
@@ -3777,10 +3773,8 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
def update_willexecutors(self, wes=None):
if not wes:
- wes = self.willexecutors_list
- self.bal_window.ping_willexecutors(wes, self.parent)
- self.willexecutors_list.update(wes)
- self.will_executor_list_widget.update()
+ wes=self.willexecutors_list
+ self.bal_window.ping_willexecutors(wes,self.save_willexecutors)
def import_json_file(self, path):
data = read_json_file(path)
@@ -3792,7 +3786,11 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
def _validate(self, data):
return data
- def save_willexecutors(self):
+ def save_willexecutors(self,wes=None):
+ if not wes:
+ wes=self.willexecutors_list
+ self.willexecutors_list.update(wes)
+ self.will_executor_list_widget.update()
Willexecutors.save(self.bal_window.bal_plugin, self.willexecutors_list)
@@ -3837,3 +3835,42 @@ class CheckAliveException(Exception):
self.timestamp_to_check = timestamp_to_check
def __str__(self):
return "Check alive expired please update it: {}".format(datetime.fromtimestamp(self.timestamp_to_check).isoformat())
+
+def log_error(exec_info,window=None):
+ _logger.error(exec_info)
+ tb=traceback.format_exc()
+ while tb is not None:
+ try:
+ frame = tb.tb_frame
+ _logger.error("file:", frame.f_code.co_filename)
+ _logger.error("name:", frame.f_code.co_name)
+ _logger.error("line:", tb.tb_lineno)
+ _logger.error("lasti:", tb.tb_lasti)
+ tb = tb.tb_next
+ except Exception as e:
+ _logger.error(tb,e)
+ tb= None
+ break
+ if window is not None:
+ window.show_error(exec_info)
+
+def export_meta_gui(electrum_window, title, exporter):
+ filter_ = "All files (*)"
+ filename = getSaveFileName(
+ parent=electrum_window,
+ title=_("Select file to save your {}".format(title)),
+ filename="BALplugin_{}_{}_{}".format(BalPlugin.chainname,str(electrum_window.wallet),title),
+ filter=filter_,
+ config=electrum_window.config,
+ )
+ if not filename:
+ return
+ try:
+ exporter(filename)
+ except FileExportFailed as e:
+ electrum_window.show_critical(str(e))
+ else:
+ electrum_window.show_message(
+ _("Your {0} were exported to '{1}'".format(title, str(filename)))
+ )
+
diff --git a/util.py b/util.py
index af79d28..df513ad 100644
--- a/util.py
+++ b/util.py
@@ -1,15 +1,13 @@
import bisect
from datetime import datetime, timedelta
-from electrum.gui.qt.util import getSaveFileName
-from electrum.i18n import _
from electrum.transaction import PartialTxOutput
-from electrum.util import FileExportFailed
LOCKTIME_THRESHOLD = 500000000
class Util:
+ @staticmethod
def locktime_to_str(locktime):
try:
locktime = int(locktime)
@@ -21,6 +19,7 @@ class Util:
pass
return str(locktime)
+ @staticmethod
def str_to_locktime(locktime):
try:
if locktime[-1] in ("y", "d", "b"):
@@ -33,6 +32,7 @@ class Util:
timestamp = dt_object.timestamp()
return int(timestamp)
+ @staticmethod
def parse_locktime_string(locktime, w=None):
try:
return int(locktime)
@@ -60,6 +60,7 @@ class Util:
pass
return 0
+ @staticmethod
def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0):
return int(
seconds
@@ -69,6 +70,7 @@ class Util:
+ blocks * 600
)
+ @staticmethod
def encode_amount(amount, decimal_point):
if Util.is_perc(amount):
return amount
@@ -78,6 +80,7 @@ class Util:
except Exception:
return 0
+ @staticmethod
def decode_amount(amount, decimal_point):
if Util.is_perc(amount):
return amount
@@ -88,12 +91,14 @@ class Util:
except Exception:
return str(amount)
+ @staticmethod
def is_perc(value):
try:
return value[-1] == "%"
except Exception:
return False
+ @staticmethod
def cmp_array(heira, heirb):
try:
if len(heira) != len(heirb):
@@ -105,11 +110,13 @@ class Util:
except Exception:
return False
+ @staticmethod
def cmp_heir(heira, heirb):
if heira[0] == heirb[0] and heira[1] == heirb[1]:
return True
return False
+ @staticmethod
def cmp_willexecutor(willexecutora, willexecutorb):
if willexecutora == willexecutorb:
return True
@@ -124,6 +131,7 @@ class Util:
return False
return False
+ @staticmethod
def search_heir_by_values(heirs, heir, values):
for h, v in heirs.items():
found = False
@@ -135,12 +143,14 @@ class Util:
return h
return False
+ @staticmethod
def cmp_heir_by_values(heira, heirb, values):
for v in values:
if heira[v] != heirb[v]:
return False
return True
+ @staticmethod
def cmp_heirs_by_values(
heirsa, heirsb, values, exclude_willexecutors=False, reverse=True
):
@@ -165,6 +175,7 @@ class Util:
else:
return True
+ @staticmethod
def cmp_heirs(
heirsa,
heirsb,
@@ -187,6 +198,7 @@ class Util:
raise e
return False
+ @staticmethod
def cmp_inputs(inputsa, inputsb):
if len(inputsa) != len(inputsb):
return False
@@ -195,6 +207,7 @@ class Util:
return False
return True
+ @staticmethod
def cmp_outputs(outputsa, outputsb, willexecutor_output=None):
if len(outputsa) != len(outputsb):
return False
@@ -204,6 +217,7 @@ class Util:
return False
return True
+ @staticmethod
def cmp_txs(txa, txb):
if not Util.cmp_inputs(txa.inputs(), txb.inputs()):
return False
@@ -211,6 +225,7 @@ class Util:
return False
return True
+ @staticmethod
def get_value_amount(txa, txb):
outputsa = txa.outputs()
# outputsb = txb.outputs()
@@ -229,6 +244,7 @@ class Util:
return value_amount
+ @staticmethod
def chk_locktime(timestamp_to_check, block_height_to_check, locktime):
# TODO BUG: WHAT HAPPEN AT THRESHOLD?
locktime = int(locktime)
@@ -239,6 +255,7 @@ class Util:
else:
return False
+ @staticmethod
def anticipate_locktime(locktime, blocks=0, hours=0, days=0):
locktime = int(locktime)
out = 0
@@ -255,6 +272,7 @@ class Util:
out = 1
return out
+ @staticmethod
def cmp_locktime(locktimea, locktimeb):
if locktimea == locktimeb:
return 0
@@ -268,17 +286,20 @@ class Util:
else:
return int(locktimea) - (locktimeb)
+ @staticmethod
def get_lowest_valid_tx(available_utxos, will):
will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
for txid, willitem in will.items():
pass
+ @staticmethod
def get_locktimes(will):
locktimes = {}
for txid, willitem in will.items():
locktimes[willitem["tx"].locktime] = True
return locktimes.keys()
+ @staticmethod
def get_lowest_locktimes(locktimes):
sorted_timestamp = []
sorted_block = []
@@ -291,18 +312,22 @@ class Util:
return sorted(sorted_timestamp), sorted(sorted_block)
+ @staticmethod
def get_lowest_locktimes_from_will(will):
return Util.get_lowest_locktimes(Util.get_locktimes(will))
+ @staticmethod
def search_willtx_per_io(will, tx):
for wid, w in will.items():
if Util.cmp_txs(w["tx"], tx["tx"]):
return wid, w
return None, None
+ @staticmethod
def invalidate_will(will):
raise Exception("not implemented")
+ @staticmethod
def get_will_spent_utxos(will):
utxos = []
for txid, willitem in will.items():
@@ -310,6 +335,7 @@ class Util:
return utxos
+ @staticmethod
def utxo_to_str(utxo):
try:
return utxo.to_str()
@@ -321,6 +347,7 @@ class Util:
pass
return str(utxo)
+ @staticmethod
def cmp_utxo(utxoa, utxob):
utxoa = Util.utxo_to_str(utxoa)
utxob = Util.utxo_to_str(utxob)
@@ -329,21 +356,25 @@ class Util:
else:
return False
+ @staticmethod
def in_utxo(utxo, utxos):
for s_u in utxos:
if Util.cmp_utxo(s_u, utxo):
return True
return False
+ @staticmethod
def txid_in_utxo(txid, utxos):
for s_u in utxos:
if s_u.prevout.txid == txid:
return True
return False
+ @staticmethod
def cmp_output(outputa, outputb):
return outputa.address == outputb.address and outputa.value == outputb.value
+ @staticmethod
def in_output(output, outputs):
for s_o in outputs:
if Util.cmp_output(s_o, output):
@@ -355,6 +386,7 @@ class Util:
# return true false same amount different address
# return false false different amount, different address not found
+ @staticmethod
def din_output(out, outputs):
same_amount = []
for s_o in outputs:
@@ -370,6 +402,7 @@ class Util:
else:
return False, False
+ @staticmethod
def get_change_output(wallet, in_amount, out_amount, fee):
change_amount = int(in_amount - out_amount - fee)
if change_amount > wallet.dust_threshold():
@@ -380,6 +413,7 @@ class Util:
out.is_change = True
return out
+ @staticmethod
def get_current_height(network):
# if no network or not up to date, just set locktime to zero
if not network:
@@ -401,6 +435,7 @@ class Util:
height = min(chain_height, server_height)
return height
+ @staticmethod
def print_var(var, name="", veryverbose=False):
print(f"---{name}---")
if var is not None:
@@ -435,6 +470,7 @@ class Util:
print(f"---end {name}---")
+ @staticmethod
def print_utxo(utxo, name=""):
print(f"---utxo-{name}---")
Util.print_var(utxo, name)
@@ -446,36 +482,20 @@ class Util:
print("_TxInput__value_sats:", utxo._TxInput__value_sats)
print(f"---utxo-end {name}---")
+ @staticmethod
def print_prevout(prevout, name=""):
print(f"---prevout-{name}---")
Util.print_var(prevout, f"{name}-prevout")
Util.print_var(prevout._asdict())
print(f"---prevout-end {name}---")
- def export_meta_gui(electrum_window, title, exporter):
- filter_ = "All files (*)"
- filename = getSaveFileName(
- parent=electrum_window,
- title=_("Select file to save your {}".format(title)),
- filename="BALplugin_{}".format(title),
- filter=filter_,
- config=electrum_window.config,
- )
- if not filename:
- return
- try:
- exporter(filename)
- except FileExportFailed as e:
- electrum_window.show_critical(str(e))
- else:
- electrum_window.show_message(
- _("Your {0} were exported to '{1}'".format(title, str(filename)))
- )
+ @staticmethod
def copy(dicto, dictfrom):
for k, v in dictfrom.items():
dicto[k] = v
+ @staticmethod
def fix_will_settings_tx_fees(will_settings):
tx_fees = will_settings.get("tx_fees", False)
have_to_update = False
@@ -485,6 +505,7 @@ class Util:
have_to_update = True
return have_to_update
+ @staticmethod
def fix_will_tx_fees(will):
have_to_update = False
for txid, willitem in will.items():
@@ -495,15 +516,18 @@ class Util:
have_to_update = True
return have_to_update
+ @staticmethod
def text_to_hex(text: str) -> str:
"""Convert text to hexadecimal string"""
hex_string = text.encode('utf-8').hex()
return hex_string
+ @staticmethod
def hex_to_text(hex_string: str) -> str:
"""Convert hexadecimal string back to text (for verification)"""
try:
return bytes.fromhex(hex_string).decode('utf-8')
except Exception:
return "Error: Invalid hex string"
+
diff --git a/wallet_util/README.md b/wallet_util/README.md
new file mode 100644
index 0000000..0d8c02f
--- /dev/null
+++ b/wallet_util/README.md
@@ -0,0 +1,50 @@
+## README
+
+### Overview
+This tool provides two entry points: a CLI script (bal_wallet_utils.py) and a Qt GUI script (bal_wallet_utils_qt.py) that operate against an Electrum source tree.
+
+### Installation / Preparation
+1. Copy both files into the Electrum project root (the folder that contains the Electrum source package):
+ - bal_wallet_utils.py
+ - bal_wallet_utils_qt.py
+
+2. Activate the Electrum Python environment (the virtualenv used to run Electrum). Example (PowerShell, adjust path to your venv):
+```
+.\env\Scripts\Activate.ps1
+```
+or (cmd):
+```
+env\Scripts\activate.bat
+```
+
+### Running
+- CLI version:
+```
+python bal_wallet_utils.py
+```
+- Qt GUI version:
+```
+python bal_wallet_utils_qt.py
+```
+
+### Building a Windows executable with PyInstaller
+From the project root (with the Electrum environment active), you can build the Qt executable using PyInstaller. Example command (adjust the paths if your environment path differs):
+```
+pyinstaller.exe --onefile --noconsole --add-data "electrum\currencies.json;electrum" --add-data "electrum\bip39_wallet_formats.json;electrum" --add-data "electrum\lnwire\peer_wire.csv;electrum\lnwire" --add-data "electrum\lnwire\onion_wire.csv;electrum\lnwire" --add-binary "env/Lib/site-packages\electrum_ecc\libsecp256k1-6.dll;electrum_ecc" bal_wallet_utils_qt.py
+```
+
+Notes:
+- Run the command from the project root so relative paths resolve correctly.
+- On Windows the --add-data and --add-binary arguments use ";" to separate source and destination.
+- If electrum expects additional data files or native DLLs, include them with additional --add-data / --add-binary flags.
+- For debugging include --onedir first to inspect the created folder before using --onefile.
+
+### Troubleshooting
+- If PyInstaller is not found, run it via Python:
+```
+python -m PyInstaller
+```
+- If the frozen exe fails because DLLs or JSON files are missing, add those files explicitly with --add-data or --add-binary.
+- Test the build on a clean Windows VM to ensure all runtime dependencies are included.
+
+License and attribution: include your preferred license or attribution details here.
diff --git a/wallet_util/bal_wallet_utils.py b/wallet_util/bal_wallet_utils.py
index f97b162..b81e0a0 100755
--- a/wallet_util/bal_wallet_utils.py
+++ b/wallet_util/bal_wallet_utils.py
@@ -1,10 +1,11 @@
#!env/bin/python3
-from electrum.storage import WalletStorage
-import json
-from electrum.util import MyEncoder
-import sys
import getpass
+import json
import os
+import sys
+
+from electrum.storage import WalletStorage
+from electrum.util import MyEncoder
default_fees = 100
@@ -26,9 +27,12 @@ def fix_will_settings_tx_fees(json_wallet):
def uninstall_bal(json_wallet):
- del json_wallet["will_settings"]
- del json_wallet["will"]
- del json_wallet["heirs"]
+ if "will_settings" in json_wallet:
+ del json_wallet["will_settings"]
+ if "will" in json_wallet:
+ del json_wallet["will"]
+ if "heirs" in json_wallet:
+ del json_wallet["heirs"]
return True
diff --git a/wallet_util/bal_wallet_utils_qt.py b/wallet_util/bal_wallet_utils_qt.py
index e3744ee..4da6b57 100755
--- a/wallet_util/bal_wallet_utils_qt.py
+++ b/wallet_util/bal_wallet_utils_qt.py
@@ -1,30 +1,31 @@
#!/usr/bin/env python3
-import sys
-import os
import json
+import os
+import sys
+
+from bal_wallet_utils import fix_will_settings_tx_fees, save, uninstall_bal
+from electrum.storage import WalletStorage
from PyQt6.QtWidgets import (
QApplication,
- QMainWindow,
- QVBoxLayout,
+ QFileDialog,
+ QGroupBox,
QHBoxLayout,
QLabel,
QLineEdit,
+ QMainWindow,
QPushButton,
- QWidget,
- QFileDialog,
- QGroupBox,
QTextEdit,
+ QVBoxLayout,
+ QWidget,
)
-from electrum.storage import WalletStorage
-from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, save
class WalletUtilityGUI(QMainWindow):
def __init__(self):
super().__init__()
- self.initUI()
+ self.init_ui()
- def initUI(self):
+ def init_ui(self):
self.setWindowTitle("BAL Wallet Utility")
self.setFixedSize(500, 400)
diff --git a/will.py b/will.py
index fcc8b61..eccd8f9 100644
--- a/will.py
+++ b/will.py
@@ -24,6 +24,7 @@ _logger = get_logger(__name__)
class Will:
+ @staticmethod
def get_children(will, willid):
out = []
for _id in will:
@@ -35,6 +36,7 @@ class Will:
return out
# build a tree with parent transactions
+ @staticmethod
def add_willtree(will):
for willid in will:
will[willid].children = Will.get_children(will, willid)
@@ -43,14 +45,17 @@ class Will:
will[child[0]].father = willid
# return a list of will sorted by locktime
+ @staticmethod
def get_sorted_will(will):
return sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
+ @staticmethod
def only_valid(will):
for k, v in will.items():
if v.get_status("VALID"):
yield k
+ @staticmethod
def search_equal_tx(will, tx, wid):
for w in will:
if w != wid and not tx.to_json() != will[w]["tx"].to_json():
@@ -59,6 +64,7 @@ class Will:
return will[w]["tx"]
return False
+ @staticmethod
def get_tx_from_any(x):
try:
a = str(x)
@@ -69,6 +75,7 @@ class Will:
return x
+ @staticmethod
def add_info_from_will(will, wid, wallet):
if isinstance(will[wid].tx, str):
will[wid].tx = Will.get_tx_from_any(will[wid].tx)
@@ -89,7 +96,9 @@ class Will:
txin._TxInput__value_sats = change.value
txin._trusted_value_sats = change.value
- def normalize_will(will, wallet=None, others_inputs={}):
+ @staticmethod
+ def normalize_will(will, wallet=None, others_inputs=None):
+ others_input = others_inputs if others_inputs is not None else {}
to_delete = []
to_add = {}
# add info from wallet
@@ -138,6 +147,7 @@ class Will:
if wid in will:
del will[wid]
+ @staticmethod
def new_input(txid, idx, change):
prevout = TxOutpoint(txid=bfh(txid), out_idx=idx)
inp = PartialTxInput(prevout=prevout)
@@ -148,6 +158,7 @@ class Will:
inp._TxInput__value_sats = change.value
return inp
+ @staticmethod
def check_anticipate(ow: "WillItem", nw: "WillItem"):
anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1)
if int(nw.tx.locktime) >= int(anticipate):
@@ -177,6 +188,7 @@ class Will:
return anticipate
return 4294967295 + 1
+ @staticmethod
def change_input(will, otxid, idx, change, others_inputs, to_delete, to_append):
ow = will[otxid]
ntxid = ow.tx.txid()
@@ -217,6 +229,7 @@ class Will:
to_append,
)
+ @staticmethod
def get_all_inputs(will, only_valid=False):
all_inputs = {}
for w, wi in will.items():
@@ -231,6 +244,7 @@ class Will:
all_inputs[prevout_str].append(inp)
return all_inputs
+ @staticmethod
def get_all_inputs_min_locktime(all_inputs):
all_inputs_min_locktime = {}
@@ -245,6 +259,7 @@ class Will:
return all_inputs_min_locktime
+ @staticmethod
def search_anticipate_rec(will, old_inputs):
redo = False
to_delete = []
@@ -284,6 +299,7 @@ class Will:
Will.search_anticipate_rec(will, old_inputs)
+ @staticmethod
def update_will(old_will, new_will):
all_old_inputs = Will.get_all_inputs(old_will, only_valid=True)
# all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
@@ -310,6 +326,7 @@ class Will:
else:
continue
+ @staticmethod
def get_higher_input_for_tx(will):
out = {}
for wid in will:
@@ -323,6 +340,7 @@ class Will:
out[inp.prevout.to_str()] = inp
return out
+ @staticmethod
def invalidate_will(will, wallet, fees_per_byte):
will_only_valid = Will.only_valid_list(will)
inputs = Will.get_all_inputs(will_only_valid)
@@ -374,11 +392,13 @@ class Will:
_logger.debug("len utxo_to_spend <=0")
pass
+ @staticmethod
def is_new(will):
for wid, w in will.items():
if w.get_status("VALID") and not w.get_status("COMPLETE"):
return True
+ @staticmethod
def search_rai(all_inputs, all_utxos, will, wallet):
# will_only_valid = Will.only_valid_or_replaced_list(will)
for inp, ws in all_inputs.items():
@@ -412,20 +432,25 @@ class Will:
else:
pass
+ @staticmethod
def utxos_strs(utxos):
return [Util.utxo_to_str(u) for u in utxos]
- def set_invalidate(wid, will=[]):
+ @staticmethod
+ def set_invalidate(wid, will=None):
+ will = will if will is not None else {}
will[wid].set_status("INVALIDATED", True)
if will[wid].children:
for c in will[wid].children.items():
Will.set_invalidate(c[0], will)
+ @staticmethod
def check_tx_height(tx, wallet):
info = wallet.get_tx_info(tx)
return info.tx_mined_status.height()
# check if transactions are stil valid tecnically valid
+ @staticmethod
def check_invalidated(willtree, utxos_list, wallet):
for wid, w in willtree.items():
if (
@@ -458,6 +483,7 @@ class Will:
# if wc.children:
# Will.reflect_to_children(wc)
+ @staticmethod
def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust):
fixed_heirs, fixed_amount, perc_heirs, perc_amount, fixed_amount_with_dust = (
heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True)
@@ -481,6 +507,7 @@ class Will:
f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}"
)
+ @staticmethod
def check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check):
Will.add_willtree(will)
utxos_list = Will.utxos_strs(all_utxos)
@@ -497,18 +524,28 @@ class Will:
Will.search_rai(all_inputs, all_utxos, will, wallet)
+ @staticmethod
+ def get_min_locktime(will,default_value=None):
+ return min((v.tx.locktime for v in will.values() if v.get_status('VALID')), default=default_value)
+
+
+
+ @staticmethod
def is_will_valid(
will,
block_to_check,
timestamp_to_check,
tx_fees,
all_utxos,
- heirs={},
- willexecutors={},
+ heirs=None,
+ willexecutors=None,
self_willexecutor=False,
wallet=False,
callback_not_valid_tx=None,
):
+ heirs = heirs if heirs is not None else {}
+ willexecutors= willexecutors if willexecutors is not None else {}
+
Will.check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check)
if heirs:
if not Will.check_willexecutors_and_heirs(
@@ -537,6 +574,7 @@ class Will:
_logger.info("will ok")
return True
+ @staticmethod
def check_will_expired(all_inputs_min_locktime, block_to_check, timestamp_to_check):
_logger.info("check if some transaction is expired")
for prevout_str, wid in all_inputs_min_locktime.items():
@@ -568,6 +606,7 @@ class Will:
# if not parentwill or not parentwill.get_status("VALID"):
# w[1].set_status("INVALIDATED", True)
+ @staticmethod
def only_valid_list(will):
out = {}
for wid, w in will.items():
@@ -575,6 +614,7 @@ class Will:
out[wid] = w
return out
+ @staticmethod
def only_valid_or_replaced_list(will):
out = []
for wid, w in will.items():
@@ -583,6 +623,7 @@ class Will:
out.append(wid)
return out
+ @staticmethod
def check_willexecutors_and_heirs(
will, heirs, willexecutors, self_willexecutor, check_date, tx_fees
):
@@ -645,6 +686,7 @@ class Will:
return True
+
class WillItem(Logger):
STATUS_DEFAULT = {
"ANTICIPATED": ["Anticipated", False],
diff --git a/willexecutors.py b/willexecutors.py
index b86655d..717f359 100644
--- a/willexecutors.py
+++ b/willexecutors.py
@@ -1,9 +1,8 @@
import json
-from datetime import datetime
import time
+from datetime import datetime
from aiohttp import ClientResponse
-from electrum import constants
from electrum.i18n import _
from electrum.logging import get_logger
from electrum.network import Network
@@ -14,11 +13,12 @@ DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__)
-chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin"
+chainname = BalPlugin.chainname
class Willexecutors:
+ @staticmethod
def save(bal_plugin, willexecutors):
_logger.debug(f"save {willexecutors},{chainname}")
aw = bal_plugin.WILLEXECUTORS.get()
@@ -27,6 +27,7 @@ class Willexecutors:
_logger.debug(f"saved: {aw}")
# bal_plugin.WILLEXECUTORS.set(willexecutors)
+ @staticmethod
def get_willexecutors(
bal_plugin, update=False, bal_window=False, force=False, task=True
):
@@ -78,6 +79,7 @@ class Willexecutors:
)
return w_sorted
+ @staticmethod
def is_selected(willexecutor, value=None):
if not willexecutor:
return False
@@ -89,6 +91,7 @@ class Willexecutors:
willexecutor["selected"] = False
return False
+ @staticmethod
def get_willexecutor_transactions(will, force=False):
willexecutors = {}
for wid, willitem in will.items():
@@ -124,6 +127,7 @@ class Willexecutors:
# willexecutors[url]["txs"], url
# )
+ @staticmethod
def send_request(
method, url, data=None, *, timeout=10, handle_response=None, count_reply=0
):
@@ -177,12 +181,14 @@ class Willexecutors:
_logger.debug(f"--> {response}")
return response
+ @staticmethod
def get_we_url_from_response(resp):
url_slices = str(resp.url).split("/")
if len(url_slices) > 2:
url_slices = url_slices[:-2]
return "/".join(url_slices)
+ @staticmethod
async def handle_response(resp: ClientResponse):
r = await resp.text()
try:
@@ -196,9 +202,11 @@ class Willexecutors:
pass
return r
+ @staticmethod
class AlreadyPresentException(Exception):
pass
+ @staticmethod
def push_transactions_to_willexecutor(willexecutor):
out = True
try:
@@ -224,10 +232,12 @@ class Willexecutors:
return out
+ @staticmethod
def ping_servers(willexecutors):
for url, we in willexecutors.items():
Willexecutors.get_info_task(url, we)
+ @staticmethod
def get_info_task(url, willexecutor):
w = None
try:
@@ -248,7 +258,9 @@ class Willexecutors:
willexecutor["last_update"] = datetime.now().timestamp()
return willexecutor
- def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}):
+ @staticmethod
+ def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor=None):
+ old_willexecutor=old_willexecutor if old_willexecutor is not None else {}
willexecutor["url"] = url
if status is not None:
willexecutor["status"] = status
@@ -260,11 +272,13 @@ class Willexecutors:
- def download_list(old_willexecutors):
+ @staticmethod
+ def download_list(old_willexecutors,welist_server):
try:
+ welist_server = welist_server if welist_server[-1] == '/' else welist_server+'/'
willexecutors = Willexecutors.send_request(
"get",
- f"https://welist.bitcoin-after.life/data/{chainname}?page=0&limit=100",
+ f"{welist_server}data/{chainname}?page=0&limit=100",
)
# del willexecutors["status"]
for w in willexecutors:
@@ -280,6 +294,7 @@ class Willexecutors:
_logger.error(f"Failed to download willexecutors list: {e}")
return {}
+ @staticmethod
def get_willexecutors_list_from_json():
try:
with open("willexecutors.json") as f:
@@ -294,6 +309,7 @@ class Willexecutors:
return {}
+ @staticmethod
def check_transaction(txid, url):
_logger.debug(f"{url}:{txid}")
try:
@@ -305,53 +321,54 @@ class Willexecutors:
_logger.error(f"error contacting {url} for checking txs {e}")
raise e
+ @staticmethod
def compute_id(willexecutor):
return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain"))
-class WillExecutor:
- def __init__(
- self,
- url,
- base_fee,
- chain,
- info,
- version,
- status,
- is_selected=False,
- promo_code="",
- ):
- self.url = url
- self.base_fee = base_fee
- self.chain = chain
- self.info = info
- self.version = version
- self.status = status
- self.promo_code = promo_code
- self.is_selected = is_selected
- self.id = self.compute_id()
-
- def from_dict(d):
- return WillExecutor(
- url=d.get("url", "http://localhost:8000"),
- base_fee=d.get("base_fee", 1000),
- chain=d.get("chain", chainname),
- info=d.get("info", ""),
- version=d.get("version", 0),
- status=d.get("status", "Ko"),
- is_selected=d.get("is_selected", "False"),
- promo_code=d.get("promo_code", ""),
- )
-
- def to_dict(self):
- return {
- "url": self.url,
- "base_fee": self.base_fee,
- "chain": self.chain,
- "info": self.info,
- "version": self.version,
- "promo_code": self.promo_code,
- }
-
- def compute_id(self):
- return f"{self.url}-{self.chain}"
+#class WillExecutor:
+# def __init__(
+# self,
+# url,
+# base_fee,
+# chain,
+# info,
+# version,
+# status,
+# is_selected=False,
+# promo_code="",
+# ):
+# self.url = url
+# self.base_fee = base_fee
+# self.chain = chain
+# self.info = info
+# self.version = version
+# self.status = status
+# self.promo_code = promo_code
+# self.is_selected = is_selected
+# self.id = self.compute_id()
+#
+# def from_dict(d):
+# return WillExecutor(
+# url=d.get("url", "http://localhost:8000"),
+# base_fee=d.get("base_fee", 1000),
+# chain=d.get("chain", chainname),
+# info=d.get("info", ""),
+# version=d.get("version", 0),
+# status=d.get("status", "Ko"),
+# is_selected=d.get("is_selected", "False"),
+# promo_code=d.get("promo_code", ""),
+# )
+#
+# def to_dict(self):
+# return {
+# "url": self.url,
+# "base_fee": self.base_fee,
+# "chain": self.chain,
+# "info": self.info,
+# "version": self.version,
+# "promo_code": self.promo_code,
+# }
+#
+# def compute_id(self):
+# return f"{self.url}-{self.chain}"