970 lines
39 KiB
Python
970 lines
39 KiB
Python
'''
|
|
|
|
Bal
|
|
|
|
Bitcoin after life
|
|
|
|
|
|
'''
|
|
|
|
import os
|
|
import random
|
|
import traceback
|
|
from functools import partial
|
|
import sys
|
|
import copy
|
|
|
|
import sys
|
|
|
|
from electrum.plugin import hook
|
|
from electrum.i18n import _
|
|
from electrum.util import make_dir, InvalidPassword, UserCancelled,resource_path
|
|
from electrum.util import bfh, read_json_file,write_json_file,decimal_point_to_base_unit_name,FileImportFailed,FileExportFailed
|
|
|
|
from electrum.gui.qt.util import (EnterButton, WWLabel,
|
|
Buttons, CloseButton, OkButton,import_meta_gui,export_meta_gui,char_width_in_lineedit,CancelButton,HelpButton)
|
|
|
|
from electrum.gui.qt.qrtextedit import ScanQRTextEdit
|
|
from electrum.gui.qt.main_window import StatusBarButton
|
|
from electrum.gui.qt.password_dialog import PasswordDialog
|
|
from electrum.gui.qt.transaction_dialog import TxDialog
|
|
from electrum import constants
|
|
from electrum.transaction import Transaction
|
|
from .bal import BalPlugin
|
|
from .heirs import Heirs
|
|
from . import util as Util
|
|
from . import will as Will
|
|
|
|
from .balqt.locktimeedit import HeirsLockTimeEdit
|
|
from .balqt.willexecutor_dialog import WillExecutorDialog
|
|
from .balqt.preview_dialog import PreviewDialog,PreviewList
|
|
from .balqt.heir_list import HeirList
|
|
from .balqt.amountedit import PercAmountEdit
|
|
from .balqt.willdetail import WillDetailDialog
|
|
from .balqt.closedialog import BalCloseDialog
|
|
from .balqt import qt_resources
|
|
from . import willexecutors as Willexecutors
|
|
from electrum.transaction import tx_from_any
|
|
import time
|
|
from electrum import json_db
|
|
from electrum.json_db import StoredDict
|
|
import datetime
|
|
import urllib.parse
|
|
import urllib.request
|
|
from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple, Mapping
|
|
from .balqt.baldialog import BalDialog,BalWaitingDialog,BalBlockingWaitingDialog,bal_checkbox
|
|
|
|
from electrum.logging import Logger
|
|
if qt_resources.QT_VERSION == 5:
|
|
from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
|
|
from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,QIcon,
|
|
QColor, QDesktopServices, qRgba, QPainterPath,QPalette)
|
|
|
|
from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QPushButton, QLineEdit,QCheckBox,QSpinBox,QMenuBar,QMenu,QLineEdit,QScrollArea,QWidget,QSpacerItem,QSizePolicy)
|
|
else:
|
|
from PyQt6.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
|
|
from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,QIcon,
|
|
QColor, QDesktopServices, qRgba, QPainterPath,QPalette)
|
|
|
|
from PyQt6.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QPushButton, QLineEdit,QCheckBox,QSpinBox,QMenuBar,QMenu,QLineEdit,QScrollArea,QWidget,QSpacerItem,QSizePolicy)
|
|
|
|
class Plugin(BalPlugin,Logger):
|
|
|
|
def __init__(self, parent, config, name):
|
|
Logger.__init__(self)
|
|
self.logger.info("INIT BALPLUGIN")
|
|
BalPlugin.__init__(self, parent, config, name)
|
|
self.bal_windows={}
|
|
|
|
|
|
@hook
|
|
def init_qt(self,gui_object):
|
|
print("********************************************************************************************************************")
|
|
self.logger.info("HOOK init qt")
|
|
print("logger")
|
|
try:
|
|
self.gui_object=gui_object
|
|
print(dir(gui_object))
|
|
for window in gui_object.windows:
|
|
wallet = window.wallet
|
|
if wallet:
|
|
window.show_warning(_('Please restart Electrum to activate the BAL plugin'), title=_('Success'))
|
|
return
|
|
w = BalWindow(self,window)
|
|
print("windows.winid",window.winid)
|
|
self.bal_windows[window.winId]= w
|
|
for child in window.children():
|
|
if isinstance(child,QMenuBar):
|
|
print("found menubar")
|
|
for menu_child in child.children():
|
|
if isinstance(menu_child,QMenu):
|
|
print("found qmenu")
|
|
try:
|
|
if menu_child.title()==_("&Tools"):
|
|
print("found tools")
|
|
w.init_menubar_tools(menu_child)
|
|
|
|
except Exception as e:
|
|
raise e
|
|
self.logger.error(("except:",menu_child.text()))
|
|
|
|
except Exception as e:
|
|
self.logger.error("Error loading plugini {}".format(e))
|
|
raise e
|
|
|
|
|
|
|
|
@hook
|
|
def create_status_bar(self, sb):
|
|
self.logger.info("HOOK create status bar")
|
|
return
|
|
b = StatusBarButton(qt_resources.read_QIcon('bal32x32.png'), "Bal "+_("Bitcoin After Life"),
|
|
partial(self.setup_dialog, sb), sb.height())
|
|
sb.addPermanentWidget(b)
|
|
|
|
@hook
|
|
def init_menubar(self,window):
|
|
self.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.info("HOOK load wallet")
|
|
w = self.get_window(main_window)
|
|
print(dir(w))
|
|
w.wallet = wallet
|
|
w.init_will()
|
|
w.willexecutors = Willexecutors.get_willexecutors(self, update=False, bal_window=w)
|
|
w.disable_plugin = False
|
|
w.ok=True
|
|
@hook
|
|
def close_wallet(self,wallet):
|
|
for winid,win in self.bal_windows.items():
|
|
if win.wallet == wallet:
|
|
win.on_close()
|
|
|
|
def get_window(self,window):
|
|
w = self.bal_windows.get(window.winId,None)
|
|
if w is None:
|
|
w=BalWindow(self,window)
|
|
self.bal_windows[window.winId]=w
|
|
return w
|
|
|
|
def requires_settings(self):
|
|
return True
|
|
|
|
def settings_widget(self, window):
|
|
|
|
w=self.get_window(window.window)
|
|
return EnterButton(_('Settings'), partial(w.settings_dialog,window))
|
|
|
|
def password_dialog(self, msg=None, parent=None):
|
|
parent = parent or self
|
|
d = PasswordDialog(parent, msg)
|
|
return d.run()
|
|
|
|
def get_seed(self):
|
|
password = None
|
|
if self.wallet.has_keystore_encryption():
|
|
password = self.password_dialog(parent=self.d.parent())
|
|
if not password:
|
|
raise UserCancelled()
|
|
|
|
keystore = self.wallet.get_keystore()
|
|
if not keystore or not keystore.has_seed():
|
|
return
|
|
self.extension = bool(keystore.get_passphrase(password))
|
|
return keystore.get_seed(password)
|
|
def settings_dialog(self,window,wallet):
|
|
|
|
d = BalDialog(window, self.get_window_title("Settings"))
|
|
d.setMinimumSize(100, 200)
|
|
qicon=qt_resources.read_QPixmap("bal32x32.png")
|
|
lbl_logo = QLabel()
|
|
lbl_logo.setPixmap(qicon)
|
|
|
|
#heir_locktime_time = QSpinBox()
|
|
#heir_locktime_time.setMinimum(0)
|
|
#heir_locktime_time.setMaximum(3650)
|
|
#heir_locktime_time.setValue(int(self.config_get(BalPlugin.LOCKTIME_TIME)))
|
|
#def on_heir_locktime_time():
|
|
# value = heir_locktime_time.value()
|
|
# self.config.set_key(BalPlugin.LOCKTIME_TIME,value,save=True)
|
|
#heir_locktime_time.valueChanged.connect(on_heir_locktime_time)
|
|
|
|
##heir_locktimedelta_time = QSpinBox()
|
|
#heir_locktimedelta_time.setMinimum(0)
|
|
#heir_locktimedelta_time.setMaximum(3650)
|
|
#heir_locktimedelta_time.setValue(int(self.config_get(BalPlugin.LOCKTIMEDELTA_TIME)))
|
|
#def on_heir_locktime_time():
|
|
#value = heir_locktime_time.value
|
|
#self.config.set_key(BalPlugin.LOCKTIME_TIME,value,save=True)
|
|
#heir_locktime_time.valueChanged.connect(on_heir_locktime_time)
|
|
|
|
#heir_locktime_blocks = QSpinBox()
|
|
#heir_locktime_blocks.setMinimum(0)
|
|
#heir_locktime_blocks.setMaximum(144*3650)
|
|
#heir_locktime_blocks.setValue(int(self.config_get(BalPlugin.LOCKTIME_BLOCKS)))
|
|
#def on_heir_locktime_blocks():
|
|
#value = heir_locktime_blocks.value()
|
|
#self.config.set_key(BalPlugin.LOCKTIME_BLOCKS,value,save=True)
|
|
#heir_locktime_blocks.valueChanged.connect(on_heir_locktime_blocks)
|
|
|
|
#heir_locktimedelta_blocks = QSpinBox()
|
|
#heir_locktimedelta_blocks.setMinimum(0)
|
|
#heir_locktimedelta_blocks.setMaximum(144*3650)
|
|
#heir_locktimedelta_blocks.setValue(int(self.config_get(BalPlugin.LOCKTIMEDELTA_BLOCKS)))
|
|
#def on_heir_locktimedelta_blocks():
|
|
#value = heir_locktimedelta_blocks.value()
|
|
#self.config.set_key(BalPlugin.LOCKTIMEDELTA_TIME,value,save=True)
|
|
#heir_locktimedelta_blocks.valueChanged.connect(on_heir_locktimedelta_blocks)
|
|
|
|
#heir_tx_fees = QSpinBox()
|
|
#heir_tx_fees.setMinimum(1)
|
|
#heir_tx_fees.setMaximum(10000)
|
|
#heir_tx_fees.setValue(int(self.config_get(BalPlugin.TX_FEES)))
|
|
#def on_heir_tx_fees():
|
|
#value = heir_tx_fees.value()
|
|
#self.config.set_key(BalPlugin.TX_FEES,value,save=True)
|
|
#heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
|
|
#heir_broadcast = bal_checkbox(self, BalPlugin.BROADCAST)
|
|
#heir_ask_broadcast = bal_checkbox(self, BalPlugin.ASK_BROADCAST)
|
|
#heir_invalidate = bal_checkbox(self, BalPlugin.INVALIDATE)
|
|
#heir_ask_invalidate = bal_checkbox(self, BalPlugin.ASK_INVALIDATE)
|
|
#heir_preview = bal_checkbox(self, BalPlugin.PREVIEW)
|
|
heir_ping_willexecutors = bal_checkbox(self, BalPlugin.PING_WILLEXECUTORS)
|
|
heir_ask_ping_willexecutors = bal_checkbox(self, BalPlugin.ASK_PING_WILLEXECUTORS)
|
|
#print("setkey broadcast")
|
|
#self.config.set_key(BalPlugin.BROADCAST,True)
|
|
heir_no_willexecutor = bal_checkbox(self, BalPlugin.NO_WILLEXECUTOR)
|
|
|
|
|
|
heir_hide_replaced = bal_checkbox(self,BalPlugin.HIDE_REPLACED,self)
|
|
|
|
heir_hide_invalidated = bal_checkbox(self,BalPlugin.HIDE_INVALIDATED,self)
|
|
#heir_allow_repush = bal_checkbox(self,BalPlugin.ALLOW_REPUSH,self)
|
|
heir_repush = QPushButton("Rebroadcast transactions")
|
|
heir_repush.clicked.connect(partial(self.broadcast_transactions,True))
|
|
grid=QGridLayout(d)
|
|
#add_widget(grid,"Refresh Time Days",heir_locktime_time,0,"Delta days for inputs to be invalidated and transactions resubmitted")
|
|
#add_widget(grid,"Refresh Blocks",heir_locktime_blocks,1,"Delta blocks for inputs to be invalidated and transaction resubmitted")
|
|
#add_widget(grid,"Transaction fees",heir_tx_fees,1,"Default transaction fees")
|
|
#add_widget(grid,"Broadcast transactions",heir_broadcast,3,"")
|
|
#add_widget(grid," - Ask before",heir_ask_broadcast,4,"")
|
|
#add_widget(grid,"Invalidate transactions",heir_invalidate,5,"")
|
|
#add_widget(grid," - Ask before",heir_ask_invalidate,6,"")
|
|
#add_widget(grid,"Show preview before sign",heir_preview,7,"")
|
|
|
|
#grid.addWidget(lbl_logo,0,0)
|
|
add_widget(grid,"Hide Replaced",heir_hide_replaced, 1, "Hide replaced transactions from will detail and list")
|
|
add_widget(grid,"Hide Invalidated",heir_hide_invalidated ,2,"Hide invalidated transactions from will detail and list")
|
|
add_widget(grid,"Ping Willexecutors",heir_ping_willexecutors,3,"Ping willexecutors to get payment info before compiling will")
|
|
add_widget(grid," - Ask before",heir_ask_ping_willexecutors,4,"Ask before to ping willexecutor")
|
|
add_widget(grid,"Backup Transaction",heir_no_willexecutor,5,"Add transactions without willexecutor")
|
|
grid.addWidget(heir_repush,6,0)
|
|
grid.addWidget(HelpButton("Broadcast all transactions to willexecutors including those already pushed"),6,2)
|
|
#add_widget(grid,"Max Allowed TimeDelta Days",heir_locktimedelta_time,8,"")
|
|
#add_widget(grid,"Max Allowed BlocksDelta",heir_locktimedelta_blocks,9,"")
|
|
|
|
if ret := bool(d.exec()):
|
|
try:
|
|
self.update_all()
|
|
return ret
|
|
except:
|
|
pass
|
|
return False
|
|
|
|
def broadcast_transactions(self,force):
|
|
for k,w in self.bal_windows.items():
|
|
print(dir(w))
|
|
w.broadcast_transactions(force)
|
|
|
|
def update_all(self):
|
|
for k,w in self.bal_windows.items():
|
|
w.update_all()
|
|
def get_window_title(self,title):
|
|
return _('BAL - ') + _(title)
|
|
|
|
class shown_cv():
|
|
_type= bool
|
|
def __init__(self,value):
|
|
self.value=value
|
|
def get(self):
|
|
return self.value
|
|
def set(self,value):
|
|
self.value=value
|
|
|
|
class BalWindow(Logger):
|
|
def __init__(self,bal_plugin: 'BalPlugin',window: 'ElectrumWindow'):
|
|
Logger.__init__(self)
|
|
self.logger.info("loggo tutto")
|
|
self.bal_plugin = bal_plugin
|
|
self.window = window
|
|
self.heirs = {}
|
|
self.will = {}
|
|
self.willitems = {}
|
|
self.willexecutors = {}
|
|
self.will_settings = None
|
|
self.heirs_tab = self.create_heirs_tab()
|
|
self.will_tab = self.create_will_tab()
|
|
self.ok= False
|
|
self.disable_plugin = True
|
|
|
|
if self.window.wallet:
|
|
self.wallet = self.window.wallet
|
|
self.heirs_tab.wallet = self.wallet
|
|
self.will_tab.wallet = self.wallet
|
|
|
|
|
|
def init_menubar_tools(self,tools_menu):
|
|
self.tools_menu=tools_menu
|
|
|
|
def add_optional_tab(tabs, tab, icon, description):
|
|
tab.tab_icon = icon
|
|
tab.tab_description = description
|
|
tab.tab_pos = len(tabs)
|
|
if tab.is_shown_cv:
|
|
tabs.addTab(tab, icon, description.replace("&", ""))
|
|
def add_toggle_action(tab):
|
|
is_shown = tab.is_shown_cv.get()
|
|
tab.menu_action = self.window.view_menu.addAction(tab.tab_description, lambda: self.window.toggle_tab(tab))
|
|
tab.menu_action.setCheckable(True)
|
|
tab.menu_action.setChecked(is_shown)
|
|
|
|
|
|
|
|
print("add tab heir",self.heirs_tab);
|
|
add_optional_tab(self.window.tabs, self.heirs_tab, qt_resources.read_QIcon("heir.png"), _("&Heirs"))
|
|
add_optional_tab(self.window.tabs, self.will_tab, qt_resources.read_QIcon("will.png"), _("&Will"))
|
|
tools_menu.addSeparator()
|
|
self.tools_menu.willexecutors_action = tools_menu.addAction(_("&Will-Executors"), self.show_willexecutor_dialog)
|
|
self.window.view_menu.addSeparator()
|
|
add_toggle_action(self.heirs_tab)
|
|
add_toggle_action(self.will_tab)
|
|
|
|
def load_willitems(self):
|
|
self.willitems={}
|
|
for wid,w in self.will.items():
|
|
self.willitems[wid]=Will.WillItem(w,wallet=self.wallet)
|
|
if self.willitems:
|
|
self.will_list.will=self.willitems
|
|
self.will_list.update_will(self.willitems)
|
|
self.will_tab.update()
|
|
|
|
def save_willitems(self):
|
|
keys = list(self.will.keys())
|
|
for k in keys:
|
|
del self.will[k]
|
|
for wid,w in self.willitems.items():
|
|
self.will[wid]=w.to_dict()
|
|
|
|
def init_will(self):
|
|
self.logger.info("********************init_____will____________**********")
|
|
if not self.willexecutors:
|
|
self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin, update=False, bal_window=self)
|
|
if not self.heirs:
|
|
self.heirs = Heirs._validate(Heirs(self.wallet.db))
|
|
if not self.will:
|
|
self.will=self.wallet.db.get_dict("will")
|
|
if self.will:
|
|
self.willitems = {}
|
|
try:
|
|
self.load_willitems()
|
|
except:
|
|
self.disable_plugin=True
|
|
self.show_warning(_('Please restart Electrum to activate the BAL plugin'), title=_('Success'))
|
|
self.close_wallet()
|
|
return
|
|
|
|
if not self.will_settings:
|
|
self.will_settings=self.wallet.db.get_dict("will_settings")
|
|
self.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))
|
|
|
|
self.bal_plugin.validate_will_settings(self.will_settings)
|
|
self.heir_list.update_will_settings()
|
|
|
|
|
|
def show_willexecutor_dialog(self):
|
|
self.willexecutor_dialog = WillExecutorDialog(self)
|
|
self.willexecutor_dialog.show()
|
|
|
|
def create_heirs_tab(self):
|
|
self.heir_list = l = HeirList(self)
|
|
|
|
print("heir_list",l)
|
|
tab = self.window.create_list_tab(l)
|
|
tab.is_shown_cv = shown_cv(True)
|
|
return tab
|
|
|
|
def create_will_tab(self):
|
|
self.will_list = l = PreviewList(self,None)
|
|
tab = self.window.create_list_tab(l)
|
|
tab.is_shown_cv = shown_cv(True)
|
|
return tab
|
|
|
|
def new_heir_dialog(self):
|
|
d = BalDialog(self.window, self.get_window_title("New heir"))
|
|
vbox = QVBoxLayout(d)
|
|
grid = QGridLayout()
|
|
|
|
heir_name = QLineEdit()
|
|
heir_name.setFixedWidth(32 * char_width_in_lineedit())
|
|
heir_address = QLineEdit()
|
|
heir_address.setFixedWidth(32 * char_width_in_lineedit())
|
|
heir_amount = PercAmountEdit(self.window.get_decimal_point)
|
|
heir_locktime = HeirsLockTimeEdit(self.window,0)
|
|
heir_is_xpub = QCheckBox()
|
|
|
|
grid.addWidget(QLabel(_("Name")), 1, 0)
|
|
grid.addWidget(heir_name, 1, 1)
|
|
grid.addWidget(HelpButton("Unique name or description about heir"),1,2)
|
|
|
|
grid.addWidget(QLabel(_("Address")), 2, 0)
|
|
grid.addWidget(heir_address, 2, 1)
|
|
grid.addWidget(HelpButton("heir bitcoin address"),2,2)
|
|
|
|
#grid.addWidget(QLabel(_("xPub")), 2, 2)
|
|
grid.addWidget(QLabel(_("Amount")),3,0)
|
|
grid.addWidget(heir_amount,3,1)
|
|
grid.addWidget(HelpButton("Fixed or Percentage amount if end with %"),3,2)
|
|
|
|
#grid.addWidget(QLabel(_("LockTime")), 4, 0)
|
|
#grid.addWidget(heir_locktime, 4, 1)
|
|
#grid.addWidget(HelpButton("if you choose Raw, you can insert various options based on suffix:\n "
|
|
# +" - b: number of blocks after current block(ex: 144b means tomorrow)\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"
|
|
# +"when using d or y time will be set to 00:00 for privacy reasons\n"
|
|
# +"when used without suffix it can be used to indicate:\n"
|
|
# +" - exact block(if value is less than 500,000,000)\n"
|
|
# +" - exact block timestamp(if value greater than 500,000,000"),4,2)
|
|
|
|
vbox.addLayout(grid)
|
|
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
|
|
while d.exec():
|
|
#TODO SAVE HEIR
|
|
heir = [
|
|
heir_name.text(),
|
|
heir_address.text(),
|
|
Util.encode_amount(heir_amount.text(),self.bal_plugin.config.get_decimal_point()),
|
|
str(heir_locktime.get_locktime()),
|
|
]
|
|
try:
|
|
self.set_heir(heir)
|
|
break
|
|
except Exception as e:
|
|
self.show_error(str(e))
|
|
|
|
def export_inheritance_handler(self,path):
|
|
txs = self.build_inheritance_transaction(ignore_duplicate=True, keep_original=False)
|
|
with open(path,"w") as f:
|
|
for tx in txs:
|
|
tx['status']+="."+BalPlugin.STATUS_EXPORTED
|
|
f.write(str(tx['tx']))
|
|
f.write('\n')
|
|
|
|
def set_heir(self,heir):
|
|
heir=list(heir)
|
|
heir[3]=self.will_settings['locktime']
|
|
|
|
h=Heirs.validate_heir(heir[0],heir[1:])
|
|
self.heirs[heir[0]]=h
|
|
self.heir_list.update()
|
|
return True
|
|
|
|
def delete_heirs(self,heirs):
|
|
for heir in heirs:
|
|
del self.heirs[heir]
|
|
self.heirs.save()
|
|
self.heir_list.update()
|
|
return True
|
|
|
|
def import_heirs(self,):
|
|
import_meta_gui(self.window, _('heirs'), self.heirs.import_file, self.heir_list.update)
|
|
|
|
def export_heirs(self):
|
|
Util.export_meta_gui(self.window, _('heirs'), self.heirs.export_file)
|
|
|
|
def prepare_will(self, ignore_duplicate = False, keep_original = False):
|
|
will = self.build_inheritance_transaction(ignore_duplicate = ignore_duplicate, keep_original=keep_original)
|
|
return will
|
|
|
|
def delete_not_valid(self,txid,s_utxo):
|
|
raise NotImplementedError()
|
|
|
|
def update_will(self,will):
|
|
Will.update_will(self.willitems,will)
|
|
self.willitems.update(will)
|
|
Will.normalize_will(self.willitems,self.wallet)
|
|
|
|
def build_will(self, ignore_duplicate = True, keep_original = True ):
|
|
|
|
will = {}
|
|
willtodelete=[]
|
|
willtoappend={}
|
|
try:
|
|
self.init_class_variables()
|
|
self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin, update=False, bal_window=self)
|
|
|
|
if not self.no_willexecutor:
|
|
f=False
|
|
for u,w in self.willexecutors.items():
|
|
if Willexecutors.is_selected(w):
|
|
f=True
|
|
if not f:
|
|
raise Will.NoWillExecutorNotPresent("No Will-Executor or backup transaction selected")
|
|
txs = self.heirs.get_transactions(self.bal_plugin,self.window.wallet,self.will_settings['tx_fees'],None,self.date_to_check)
|
|
self.logger.info(txs)
|
|
creation_time = time.time()
|
|
if txs:
|
|
for txid in txs:
|
|
txtodelete=[]
|
|
_break = False
|
|
tx = {}
|
|
tx['tx'] = txs[txid]
|
|
tx['my_locktime'] = txs[txid].my_locktime
|
|
tx['heirsvalue'] = txs[txid].heirsvalue
|
|
tx['description'] = txs[txid].description
|
|
tx['willexecutor'] = copy.deepcopy(txs[txid].willexecutor)
|
|
tx['status'] = _("New")
|
|
tx['tx_fees'] = txs[txid].tx_fees
|
|
tx['time'] = creation_time
|
|
tx['heirs'] = copy.deepcopy(txs[txid].heirs)
|
|
tx['txchildren'] = []
|
|
will[txid]=Will.WillItem(tx,_id=txid,wallet=self.wallet)
|
|
self.update_will(will)
|
|
except Exception as e:
|
|
raise e
|
|
pass
|
|
return self.willitems
|
|
|
|
def check_will(self):
|
|
return Will.is_will_valid(self.willitems, self.block_to_check, self.date_to_check, self.will_settings['tx_fees'],self.window.wallet.get_utxos(),heirs=self.heirs,willexecutors=self.willexecutors ,self_willexecutor=self.no_willexecutor, wallet = self.wallet, callback_not_valid_tx=self.delete_not_valid)
|
|
def show_message(self,text):
|
|
self.window.show_message(text)
|
|
def show_warning(self,text,parent =None):
|
|
self.window.show_warning(text, parent= None)
|
|
def show_error(self,text):
|
|
self.window.show_error(text)
|
|
def show_critical(self,text):
|
|
self.window.show_critical(text)
|
|
|
|
|
|
|
|
def init_heirs_to_locktime(self):
|
|
for heir in self.heirs:
|
|
h=self.heirs[heir]
|
|
self.heirs[heir]=[h[0],h[1],self.will_settings['locktime']]
|
|
|
|
|
|
def init_class_variables(self):
|
|
if not self.heirs:
|
|
raise Will.NoHeirsException()
|
|
return
|
|
try:
|
|
self.date_to_check = Util.parse_locktime_string(self.will_settings['threshold'])
|
|
found = False
|
|
self.locktime_blocks=self.bal_plugin.config_get(BalPlugin.LOCKTIME_BLOCKS)
|
|
self.current_block = Util.get_current_height(self.wallet.network)
|
|
self.block_to_check = self.current_block + self.locktime_blocks
|
|
self.no_willexecutor = self.bal_plugin.config_get(BalPlugin.NO_WILLEXECUTOR)
|
|
self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin,update=True,bal_window=self,task=False)
|
|
self.init_heirs_to_locktime()
|
|
|
|
except Exception as e:
|
|
self.logger.error(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")
|
|
return
|
|
if not self.heirs:
|
|
self.logger.warning("not heirs {}".format(self.heirs))
|
|
return
|
|
self.init_class_variables()
|
|
try:
|
|
Will.check_amounts(self.heirs,self.willexecutors,self.window.wallet.get_utxos(),self.date_to_check,self.window.wallet.dust_threshold())
|
|
except Will.AmountException as e:
|
|
self.show_warning(_(f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.\n{e}"))
|
|
locktime = Util.parse_locktime_string(self.will_settings['locktime'])
|
|
if locktime < self.date_to_check:
|
|
self.show_error(_("locktime is lower than threshold"))
|
|
return
|
|
if not self.no_willexecutor:
|
|
f=False
|
|
for k,we in self.willexecutors.items():
|
|
if Willexecutors.is_selected(we):
|
|
f=True
|
|
if not f:
|
|
self.show_error(_(" no backup transaction or willexecutor selected"))
|
|
return
|
|
|
|
try:
|
|
self.check_will()
|
|
except Will.WillExpiredException as e:
|
|
self.invalidate_will()
|
|
return
|
|
except Will.NoHeirsException:
|
|
return
|
|
except Will.NotCompleteWillException as e:
|
|
self.logger.info("{}:{}".format(type(e),e))
|
|
message = False
|
|
if isinstance(e,Will.HeirChangeException):
|
|
message ="Heirs changed:"
|
|
elif isinstance(e,Will.WillExecutorNotPresent):
|
|
message = "Will-Executor not present:"
|
|
elif isinstance(e,Will.WillexecutorChangeException):
|
|
message = "Will-Executor changed"
|
|
elif isinstance(e,Will.TxFeesChangedException):
|
|
message = "Txfees are changed"
|
|
elif isinstance(e,Will.HeirNotFoundException):
|
|
message = "Heir not found"
|
|
|
|
if message:
|
|
self.show_message(f"{_(message)}:\n {e}\n{_('will have to be built')}")
|
|
|
|
self.logger.info("build will")
|
|
self.build_will(ignore_duplicate,keep_original)
|
|
|
|
try:
|
|
self.check_will()
|
|
for wid,w in self.willitems.items():
|
|
self.wallet.set_label(wid,"BAL Transaction")
|
|
except Will.WillExpiredException as e:
|
|
self.invalidate_will()
|
|
except Will.NotCompleteWillException as e:
|
|
self.show_error("Error:{}\n {}".format(str(e),_("Please, check your heirs, locktime and threshold!")))
|
|
|
|
self.window.history_list.update()
|
|
self.window.utxo_list.update()
|
|
self.update_all()
|
|
return self.willitems
|
|
except Exception as e:
|
|
raise e
|
|
def show_transaction_real(
|
|
self,
|
|
tx: Transaction,
|
|
*,
|
|
parent: 'ElectrumWindow',
|
|
prompt_if_unsaved: bool = False,
|
|
external_keypairs: Mapping[bytes, bytes] = None,
|
|
payment_identifier: 'PaymentIdentifier' = None,
|
|
):
|
|
try:
|
|
d = TxDialog(
|
|
tx,
|
|
parent=parent,
|
|
prompt_if_unsaved=prompt_if_unsaved,
|
|
external_keypairs=external_keypairs,
|
|
payment_identifier=payment_identifier,
|
|
)
|
|
d.setWindowIcon(qt_resources.read_QIcon("bal32x32.png"))
|
|
except SerializationError as e:
|
|
self.logger.error('unable to deserialize the transaction')
|
|
parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
|
|
else:
|
|
d.show()
|
|
return d
|
|
|
|
def show_transaction(self,tx=None,txid=None,parent = None):
|
|
if not parent:
|
|
parent = self.window
|
|
if txid !=None and txid in self.willitems:
|
|
tx=self.willitems[txid].tx
|
|
if not tx:
|
|
raise Exception(_("no tx"))
|
|
return self.show_transaction_real(tx,parent=parent)
|
|
|
|
def invalidate_will(self):
|
|
def on_success(result):
|
|
if result:
|
|
self.show_message(_("Please sign and broadcast this transaction to invalidate current will"))
|
|
self.wallet.set_label(result.txid(),"BAL Invalidate")
|
|
a=self.show_transaction(result)
|
|
else:
|
|
self.show_message(_("No transactions to invalidate"))
|
|
def on_failure(exec_info):
|
|
self.show_error(f"ERROR:{exec_info}")
|
|
|
|
fee_per_byte=self.will_settings.get('tx_fees',1)
|
|
task = partial(Will.invalidate_will,self.willitems,self.wallet,fee_per_byte)
|
|
msg = _("Calculating Transactions")
|
|
self.waiting_dialog = BalWaitingDialog(self, msg, task, on_success, on_failure,exe=False)
|
|
self.waiting_dialog.exe()
|
|
|
|
def sign_transactions(self,password):
|
|
try:
|
|
txs={}
|
|
signed = None
|
|
tosign = None
|
|
def get_message():
|
|
msg = ""
|
|
if signed:
|
|
msg=_(f"signed: {signed}\n")
|
|
return msg + _(f"signing: {tosign}")
|
|
for txid in Will.only_valid(self.willitems):
|
|
wi = self.willitems[txid]
|
|
tx = copy.deepcopy(wi.tx)
|
|
if wi.get_status('COMPLETE'):
|
|
txs[txid]=tx
|
|
continue
|
|
tosign=txid
|
|
try:
|
|
self.waiting_dialog.update(get_message())
|
|
except:pass
|
|
for txin in tx.inputs():
|
|
prevout = txin.prevout.to_json()
|
|
if prevout[0] in self.willitems:
|
|
change = self.willitems[prevout[0]].tx.outputs()[prevout[1]]
|
|
txin._trusted_value_sats = change.value
|
|
try:
|
|
txin.script_descriptor = change.script_descriptor
|
|
except:
|
|
pass
|
|
txin.is_mine=True
|
|
txin._TxInput__address=change.address
|
|
txin._TxInput__scriptpubkey = change.scriptpubkey
|
|
txin._TxInput__value_sats = change.value
|
|
|
|
self.wallet.sign_transaction(tx, password,ignore_warnings=True)
|
|
signed=tosign
|
|
is_complete=False
|
|
if tx.is_complete():
|
|
is_complete=True
|
|
wi.set_status('COMPLETE',True)
|
|
txs[txid]=tx
|
|
except Exception as e:
|
|
return None
|
|
return txs
|
|
|
|
def get_wallet_password(self,message=None,parent=None):
|
|
parent =self.window if not parent else parent
|
|
password = None
|
|
if self.wallet.has_keystore_encryption():
|
|
password = self.bal_plugin.password_dialog(parent=parent,msg=message)
|
|
if password is None:
|
|
return False
|
|
try:
|
|
self.wallet.check_password(password)
|
|
except Exception as e:
|
|
self.show_error(str(e))
|
|
password = self.get_wallet_password(message)
|
|
return password
|
|
|
|
|
|
def on_close(self):
|
|
try:
|
|
if not self.disable_plugin:
|
|
close_window=BalCloseDialog(self)
|
|
close_window.close_plugin_task()
|
|
self.save_willitems()
|
|
self.heirs_tab.close()
|
|
self.will_tab.close()
|
|
self.tools_menu.removeAction(self.tools_menu.willexecutors_action)
|
|
self.window.toggle_tab(self.heirs_tab)
|
|
self.window.toggle_tab(self.will_tab)
|
|
self.window.tabs.update()
|
|
except Exception as e:
|
|
pass
|
|
|
|
def ask_password_and_sign_transactions(self,callback=None):
|
|
def on_success(txs):
|
|
if txs:
|
|
for txid,tx in txs.items():
|
|
self.willitems[txid].tx=copy.deepcopy(tx)
|
|
self.will[txid]=self.willitems[txid].to_dict()
|
|
try:
|
|
self.will_list.update()
|
|
except:
|
|
pass
|
|
if callback:
|
|
try:
|
|
callback()
|
|
except Exception as e:
|
|
raise e
|
|
|
|
def on_failure(exc_info):
|
|
self.logger.info("sign fail: {}".format(exc_info))
|
|
self.show_error(exc_info)
|
|
password= self.get_wallet_password()
|
|
task = partial(self.sign_transactions,password)
|
|
msg = _('Signing transactions...')
|
|
self.waiting_dialog = BalWaitingDialog(self, msg, task, on_success, on_failure,exe=False)
|
|
self.waiting_dialog.exe()
|
|
|
|
def broadcast_transactions(self,force=False):
|
|
def on_success(sulcess):
|
|
self.will_list.update()
|
|
if sulcess:
|
|
self.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")
|
|
self.show_message(_("All transactions are broadcasted to respective Will-Executors"))
|
|
|
|
def on_failure(err):
|
|
self.logger.error(err)
|
|
|
|
task = partial(self.push_transactions_to_willexecutors,force)
|
|
msg = _('Selecting Will-Executors')
|
|
self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False)
|
|
self.waiting_dialog.exe()
|
|
|
|
def push_transactions_to_willexecutors(self,force=False):
|
|
willexecutors = Willexecutors.get_willexecutor_transactions(self.willitems)
|
|
def getMsg(willexecutors):
|
|
msg = "Broadcasting Transactions to Will-Executors:\n"
|
|
for url in willexecutors:
|
|
msg += f"{url}:\t{willexecutors[url]['broadcast_status']}\n"
|
|
return msg
|
|
error=False
|
|
for url in willexecutors:
|
|
willexecutor = willexecutors[url]
|
|
self.waiting_dialog.update(getMsg(willexecutors))
|
|
if 'txs' in willexecutor:
|
|
try:
|
|
if Willexecutors.push_transactions_to_willexecutor(willexecutors[url]):
|
|
for wid in willexecutors[url]['txsids']:
|
|
self.willitems[wid].set_status('PUSHED', True)
|
|
willexecutors[url]['broadcast_status'] = _("Success")
|
|
else:
|
|
for wid in willexecutors[url]['txsids']:
|
|
self.willitems[wid].set_status('PUSH_FAIL', True)
|
|
error=True
|
|
willexecutors[url]['broadcast_status'] = _("Failed")
|
|
del willexecutor['txs']
|
|
except Willexecutors.AlreadyPresentException:
|
|
for wid in willexecutor['txsids']:
|
|
row = self.waiting_dialog.update("checking {} - {} : {}".format(self.willitems[wid].we['url'],wid, "Waiting"))
|
|
self.willitems[wid].check_willexecutor()
|
|
row = self.waiting_dialog.update("checked {} - {} : {}".format(self.willitems[wid].we['url'],wid,self.willitems[wid].get_status("CHECKED" )))
|
|
|
|
if error:
|
|
return True
|
|
|
|
|
|
def export_json_file(self,path):
|
|
for wid in self.willitems:
|
|
self.willitems[wid].set_status('EXPORTED', True)
|
|
self.will[wid]=self.willitems[wid].to_dict()
|
|
write_json_file(path, self.will)
|
|
|
|
def export_will(self):
|
|
try:
|
|
Util.export_meta_gui(self.window, _('will.json'), self.export_json_file)
|
|
except Exception as e:
|
|
self.show_error(str(e))
|
|
raise e
|
|
|
|
def import_will(self):
|
|
def sulcess():
|
|
self.will_list.update_will(self.willitems)
|
|
import_meta_gui(self.window, _('will'), self.import_json_file,sulcess)
|
|
|
|
def import_json_file(self,path):
|
|
try:
|
|
data = read_json_file(path)
|
|
willitems = {}
|
|
for k,v in data.items():
|
|
data[k]['tx']=tx_from_any(v['tx'])
|
|
willitems[k]=Will.WillItem(data[k],_id=k)
|
|
self.update_will(willitems)
|
|
except Exception as e:
|
|
raise e
|
|
raise FileImportFailed(_("Invalid will file"))
|
|
|
|
def check_transactions_task(self,will):
|
|
start = time.time()
|
|
for wid,w in will.items():
|
|
if w.we:
|
|
self.waiting_dialog.update("checking transaction: {}\n willexecutor: {}".format(wid,w.we['url']))
|
|
w.check_willexecutor()
|
|
|
|
if time.time()-start < 3:
|
|
time.sleep(3-(time.time()-start))
|
|
|
|
def check_transactions(self,will):
|
|
def on_success(result):
|
|
del self.waiting_dialog
|
|
self.update_all()
|
|
pass
|
|
def on_failure(e):
|
|
self.logger.error(f"error checking transactions {e}")
|
|
pass
|
|
|
|
task = partial(self.check_transactions_task,will)
|
|
msg = _('Check Transaction')
|
|
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")
|
|
pinged = []
|
|
failed = []
|
|
def get_title():
|
|
msg = _('Ping Will-Executors:')
|
|
msg += "\n\n"
|
|
for url in wes:
|
|
urlstr = "{:<50}: ".format(url[:50])
|
|
if url in pinged:
|
|
urlstr += "Ok"
|
|
elif url in failed:
|
|
urlstr +="Ko"
|
|
else:
|
|
urlstr += "--"
|
|
urlstr+="\n"
|
|
msg+=urlstr
|
|
|
|
return msg
|
|
for url,we in wes.items():
|
|
try:
|
|
self.waiting_dialog.update(get_title())
|
|
except:
|
|
pass
|
|
wes[url]=Willexecutors.get_info_task(url,we)
|
|
if wes[url]['status']=='KO':
|
|
failed.append(url)
|
|
else:
|
|
pinged.append(url)
|
|
|
|
def ping_willexecutors(self,wes):
|
|
def on_success(result):
|
|
del self.waiting_dialog
|
|
try:
|
|
self.willexecutor_dialog.willexecutor_list.update()
|
|
except Exception as e:
|
|
_logger.error("error updating willexecutors {e}")
|
|
pass
|
|
def on_failure(e):
|
|
self.logger.error(e)
|
|
pass
|
|
self.logger.info("ping willexecutors")
|
|
task = partial(self.ping_willexecutors_task,wes)
|
|
msg = _('Ping Will-Executors')
|
|
self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False)
|
|
self.waiting_dialog.exe()
|
|
|
|
def preview_modal_dialog(self):
|
|
self.dw=WillDetailDialog(self)
|
|
self.dw.show()
|
|
|
|
|
|
def update_all(self):
|
|
self.will_list.update_will(self.willitems)
|
|
self.heirs_tab.update()
|
|
self.will_tab.update()
|
|
self.will_list.update()
|
|
|
|
def add_widget(grid,label,widget,row,help_):
|
|
grid.addWidget(QLabel(_(label)),row,0)
|
|
grid.addWidget(widget,row,1)
|
|
grid.addWidget(HelpButton(help_),row,2)
|