Compare commits

..

2 Commits

Author SHA1 Message Date
461b0cb368
Wizard 2025-08-24 20:28:26 -04:00
d6b37005e8
balconfig 2025-08-11 16:44:55 -04:00
6 changed files with 528 additions and 181 deletions

143
bal.py
View File

@ -9,112 +9,111 @@ import os
json_db.register_dict('heirs', tuple, None)
json_db.register_dict('will', lambda x: get_will(x), None)
json_db.register_dict('will_settings', lambda x:x, None)
from electrum.logging import get_logger
def get_will(x):
try:
x['tx']=tx_from_any(x['tx'])
except Exception as e:
raise e
return x
class BalConfig():
def __init__(self, config, name, default):
self.config = config
self.name = name
self.default = default
def get(self,default=None):
v = self.config.get(self.name, default)
if v is None:
if not default is None:
v = default
else:
v = v.default
return v
def set(self,value,save=True):
self.config.set_key(self.name,value,save=save)
class BalPlugin(BasePlugin):
LOCKTIME_TIME = "bal_locktime_time"
LOCKTIME_BLOCKS = "bal_locktime_blocks"
LOCKTIMEDELTA_TIME = "bal_locktimedelta_time"
LOCKTIMEDELTA_BLOCKS = "bal_locktimedelta_blocks"
TX_FEES = "bal_tx_fees"
BROADCAST = "bal_broadcast"
ASK_BROADCAST = "bal_ask_broadcast"
INVALIDATE = "bal_invalidate"
ASK_INVALIDATE = "bal_ask_invalidate"
PREVIEW = "bal_preview"
SAVE_TXS = "bal_save_txs"
WILLEXECUTORS = "bal_willexecutors"
PING_WILLEXECUTORS = "bal_ping_willexecutors"
ASK_PING_WILLEXECUTORS = "bal_ask_ping_willexecutors"
NO_WILLEXECUTOR = "bal_no_willexecutor"
HIDE_REPLACED = "bal_hide_replaced"
HIDE_INVALIDATED = "bal_hide_invalidated"
ALLOW_REPUSH = "bal_allow_repush"
LATEST_VERSION = '1'
KNOWN_VERSIONS = ('0', '1')
assert LATEST_VERSION in KNOWN_VERSIONS
def version():
try:
f=""
with open("VERSION","r") as f:
f = str(f.readline())
return f
except:
return "unknown"
SIZE = (159, 97)
def __init__(self, parent, config, name):
self.logger = get_logger(__name__)
BasePlugin.__init__(self, parent, config, name)
self.base_dir = os.path.join(config.electrum_path(), 'bal')
self.plugin_dir = os.path.split(os.path.realpath(__file__))[0]
self.logger.info(self.base_dir)
self.parent = parent
self.config = config
self.name = name
DEFAULT_SETTINGS={
LOCKTIME_TIME: 90,
LOCKTIME_BLOCKS: 144*90,
LOCKTIMEDELTA_TIME: 7,
LOCKTIMEDELTA_BLOCKS:144*7,
TX_FEES: 100,
BROADCAST: True,
ASK_BROADCAST: True,
INVALIDATE: True,
ASK_INVALIDATE: True,
PREVIEW: True,
SAVE_TXS: True,
PING_WILLEXECUTORS: False,
ASK_PING_WILLEXECUTORS: False,
NO_WILLEXECUTOR: False,
HIDE_REPLACED:True,
HIDE_INVALIDATED:True,
ALLOW_REPUSH: False,
WILLEXECUTORS: {
'https://bitcoin-after.life:9137': {
self.ASK_BROADCAST = BalConfig(config, "bal_ask_broadcast",True)
self.BROADCAST = BalConfig(config, "bal_broadcast",True)
self.LOCKTIME_TIME = BalConfig(config, "bal_locktime_time",90)
self.LOCKTIME_BLOCKS = BalConfig(config, "bal_locktime_blocks",144*90)
self.LOCKTIMEDELTA_TIME = BalConfig(config, "bal_locktimedelta_time",7)
self.LOCKTIMEDELTA_BLOCKS = BalConfig(config, "bal_locktimedelta_blocks",144*7)
self.TX_FEES = BalConfig(config, "bal_tx_fees",100)
self.INVALIDATE = BalConfig(config, "bal_invalidate",True)
self.ASK_INVALIDATE = BalConfig(config, "bal_ask_invalidate",True)
self.PREVIEW = BalConfig(config, "bal_preview",True)
self.SAVE_TXS = BalConfig(config, "bal_save_txs",True)
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors",True)
self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors",True)
self.ASK_PING_WILLEXECUTORS = BalConfig(config, "bal_ask_ping_willexecutors",True)
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor",True)
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced",True)
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.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", {
"mainnet": {
'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", {
'tx_fees':100,
'threshold':'180d',
'locktime':'1y',
})
LATEST_VERSION = '1'
KNOWN_VERSIONS = ('0', '1')
assert LATEST_VERSION in KNOWN_VERSIONS
SIZE = (159, 97)
self._hide_invalidated= self.HIDE_INVALIDATED.get()
self._hide_replaced= self.HIDE_REPLACED.get()
def __init__(self, parent, config, name):
self.logger= get_logger(__name__)
BasePlugin.__init__(self, parent, config, name)
self.base_dir = os.path.join(config.electrum_path(), 'bal')
self.logger.info(self.base_dir)
self.parent = parent
self.config = config
self.name = name
self._hide_invalidated= self.config_get(self.HIDE_INVALIDATED)
self._hide_replaced= self.config_get(self.HIDE_REPLACED)
self.plugin_dir = os.path.split(os.path.realpath(__file__))[0]
def resource_path(self,*parts):
return os.path.join(self.plugin_dir, *parts)
def config_get(self,key):
v = self.config.get(key,None)
if v is None:
self.config.set_key(key,self.DEFAULT_SETTINGS[key])
v = self.DEFAULT_SETTINGS[key]
return v
def hide_invalidated(self):
self._hide_invalidated = not self._hide_invalidated
self.config.set_key(BalPlugin.HIDE_INVALIDATED,self.hide_invalidated,save=True)
self.HIDE_INVALIDATED.set(self._hide_invalidated)
def hide_replaced(self):
self._hide_replaced = not self._hide_replaced
self.config.set_key(BalPlugin.HIDE_REPLACED,self.hide_invalidated,save=True)
self.HIDE_REPLACED.set(self._hide_replaced)
def default_will_settings(self):
return {
'tx_fees':100,
'threshold':'180d',
'locktime':'1y',
}
def validate_will_settings(self,will_settings):
if int(will_settings.get('tx_fees',1))<1:
will_settings['tx_fees']=1

View File

@ -14,7 +14,6 @@ from electrum.transaction import PartialTxInput, PartialTxOutput,TxOutpoint,Part
import datetime
import urllib.request
import urllib.parse
from .bal import BalPlugin
from .util import Util
from .willexecutors import Willexecutors
if TYPE_CHECKING:
@ -387,7 +386,7 @@ class Heirs(dict, Logger):
utxos = wallet.get_utxos()
willexecutors = Willexecutors.get_willexecutors(bal_plugin) or {}
self.decimal_point=bal_plugin.config.get_decimal_point()
no_willexecutors = bal_plugin.config_get(BalPlugin.NO_WILLEXECUTOR)
no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get()
for utxo in utxos:
if utxo.value_sats()> 0*tx_fees:
balance += utxo.value_sats()

View File

@ -1,8 +1,9 @@
{
"name": "BAL",
"fullname": "Bitcoin After Life",
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.0b",
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.1b",
"author":"Svatantrya",
"available_for": ["qt"],
"icon":"icons/bal32x32.png"
}
}

475
qt.py
View File

@ -244,24 +244,18 @@ if TYPE_CHECKING:
from electrum.gui.qt.main_window import ElectrumWindow
from electrum.gui.qt.util import (
Buttons,
read_QIcon,
import_meta_gui,
export_meta_gui,
MessageBoxMixin,
Buttons,
export_meta_gui,
MessageBoxMixin,
char_width_in_lineedit,
ColorScheme,
Buttons,
CancelButton,
char_width_in_lineedit,
CloseButton,
ColorScheme,
EnterButton,
export_meta_gui,
HelpButton,
import_meta_gui,
MessageBoxMixin,
OkButton,
read_QIcon,
TaskThread,
WindowModalDialog,
WWLabel,
@ -418,8 +412,11 @@ class Plugin(BalPlugin,Logger):
def settings_widget(self, window):
w=self.get_window(window.window)
return EnterButton(_('Settings'), partial(w.settings_dialog,window))
widget=QWidget()
enterbutton=EnterButton(_('Settings'), partial(w.settings_dialog,window))
widget.setLayout(Buttons(enterbutton,widget))
return widget
def password_dialog(self, msg=None, parent=None):
parent = parent or self
d = PasswordDialog(parent, msg)
@ -446,14 +443,14 @@ class Plugin(BalPlugin,Logger):
lbl_logo = QLabel()
lbl_logo.setPixmap(qicon)
heir_ping_willexecutors = bal_checkbox(self, BalPlugin.PING_WILLEXECUTORS)
heir_ask_ping_willexecutors = bal_checkbox(self, BalPlugin.ASK_PING_WILLEXECUTORS)
heir_no_willexecutor = bal_checkbox(self, BalPlugin.NO_WILLEXECUTOR)
heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS)
heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS)
heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR)
heir_hide_replaced = bal_checkbox(self,BalPlugin.HIDE_REPLACED,self)
heir_hide_replaced = bal_checkbox(self.HIDE_REPLACED,self)
heir_hide_invalidated = bal_checkbox(self,BalPlugin.HIDE_INVALIDATED,self)
heir_hide_invalidated = bal_checkbox(self.HIDE_INVALIDATED,self)
heir_repush = QPushButton("Rebroadcast transactions")
heir_repush.clicked.connect(partial(self.broadcast_transactions,True))
grid=QGridLayout(d)
@ -582,36 +579,55 @@ class BalWindow(Logger):
self.bal_plugin.validate_will_settings(self.will_settings)
self.heir_list.update_will_settings()
def init_wizard(self):
wizard_dialog = BalWizardDialog(self)
wizard_dialog.exec()
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)
self.heir_list = l = HeirList(self,self.window)
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)
self.will_list = l = PreviewList(self,self.window,None)
tab = self.window.create_list_tab(l)
tab.is_shown_cv = shown_cv(True)
return tab
def new_heir_dialog(self):
def new_heir_dialog(self,heir=None):
d = BalDialog(self.window, self.bal_plugin.get_window_title("New heir"))
vbox = QVBoxLayout(d)
grid = QGridLayout()
heir_name = QLineEdit()
heir_name.setFixedWidth(32 * char_width_in_lineedit())
if heir:
heir_name.setText(heir[0])
heir_address = QLineEdit()
heir_address.setFixedWidth(32 * char_width_in_lineedit())
if heir:
heir_address.setText(heir[1])
heir_amount = PercAmountEdit(self.window.get_decimal_point)
if heir:
heir_amount.setText(heir[2])
heir_locktime = HeirsLockTimeEdit(self.window,0)
if heir:
heir_locktime.setText(heir[3])
heir_is_xpub = QCheckBox()
new_heir_button=QPushButton(_("Add another heir"))
self.add_another_heir=False
def new_heir():
self.add_another_heir=True
d.accept()
new_heir_button.clicked.connect(new_heir)
new_heir_button.setDefault(True)
grid.addWidget(QLabel(_("Name")), 1, 0)
grid.addWidget(heir_name, 1, 1)
@ -626,7 +642,7 @@ class BalWindow(Logger):
grid.addWidget(HelpButton("Fixed or Percentage amount if end with %"),3,2)
vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
vbox.addLayout(Buttons(CancelButton(d), OkButton(d),new_heir_button))
while d.exec():
#TODO SAVE HEIR
heir = [
@ -637,17 +653,19 @@ class BalWindow(Logger):
]
try:
self.set_heir(heir)
if self.add_another_heir:
self.new_heir_dialog()
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 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)
@ -750,10 +768,10 @@ class BalWindow(Logger):
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.locktime_blocks=self.bal_plugin.LOCKTIME_BLOCKS.get()
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.no_willexecutor = self.bal_plugin.NO_WILLEXECUTOR.get()
self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin,update=True,bal_window=self,task=False)
self.init_heirs_to_locktime()
@ -943,8 +961,8 @@ class BalWindow(Logger):
def on_close(self):
try:
if not self.disable_plugin:
close_window=BalCloseDialog(self)
close_window.close_plugin_task()
close_window=BalBuildWillDialog(self)
close_window.build_will_task()
self.save_willitems()
self.heirs_tab.close()
self.will_tab.close()
@ -1440,6 +1458,256 @@ class BalDialog(WindowModalDialog):
WindowModalDialog.__init__(self,self.parent,title)
self.setWindowIcon(read_bal_QIcon(icon))
class BalWizardDialog(BalDialog):
def __init__(self,bal_window: 'BalWindow' ):
assert bal_window
BalDialog.__init__(self, bal_window.window, _("Bal Wizard Setup"))
self.setMinimumSize(800, 400)
self.bal_window = bal_window
self.parent = bal_window.window
self.layout=QVBoxLayout(self)
self.widget = BalWizardHeirsWidget(bal_window,self,self.on_next_heir,None,self.on_cancel_heir)
self.layout.addWidget(self.widget)
def next_widget(self,widget):
self.layout.removeWidget(self.widget)
self.widget.close()
self.widget=widget
self.layout.addWidget(self.widget)
#self.update()
#self.repaint()
def on_next_heir(self):
self.next_widget(BalWizardLocktimeAndFeeWidget(self.bal_window,self,self.on_next_locktimeandfee,self.on_previous_heir,self.on_cancel_heir))
def on_previous_heir(self):
self.next_widget(BalWizardHeirsWidget(self.bal_window,self,self.on_next_heir,None,self.on_cancel_heir))
def on_cancel_heir(self):
pass
def on_next_wedonwload(self):
self.next_widget(BalWizardWEWidget(self.bal_window,self,self.on_next_we,self.on_next_locktimeandfee,self.on_cancel_heir))
def on_next_we(self):
close_window=BalBuildWillDialog(self.bal_window,self)
close_window.build_will_task()
self.close()
#self.next_widget(BalWizardLocktimeAndFeeWidget(self.bal_window,self,self.on_next_locktimeandfee,self.on_next_wedonwload,self.on_next_wedonwload.on_cancel_heir))
def on_next_locktimeandfee(self):
self.next_widget(BalWizardWEDownloadWidget(self.bal_window,self,self.on_next_wedonwload,self.on_next_heir,self.on_cancel_heir))
def on_accept(self):
print("accepted")
def on_reject(self):
print("rejected")
def on_close(self):
print("close")
def closeEvent(self,event):
self.bal_window.update_all()
self.bal_window.heir_list.update_will_settings()
print("close event")
class BalWizardWidget(QWidget):
title = None
message = None
def __init__(self,bal_window: 'BalWindow',parent,on_next,on_previous,on_cancel):
QWidget.__init__(self,parent)
self.vbox=QVBoxLayout(self)
self.bal_window=bal_window
self.parent=parent
self.on_next=on_next
self.on_cancel=on_cancel
self.titleLabel = QLabel(self.title)
self.vbox.addWidget(self.titleLabel)
self.messageLabel = QLabel(_(self.message))
self.vbox.addWidget(self.messageLabel)
self.content = self.get_content()
self.content_container= QWidget()
self.containrelayout=QVBoxLayout(self.content_container)
self.containrelayout.addWidget(self.content)
self.vbox.addWidget(self.content_container)
spacer_widget = QWidget()
spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.vbox.addWidget(spacer_widget)
self.buttons=[]
if on_previous:
self.on_previous=on_previous
self.previous_button = QPushButton(_("Previous"))
self.previous_button.clicked.connect(self._on_previous)
self.buttons.append(self.previous_button)
self.next_button = QPushButton(_("Next"))
self.next_button.clicked.connect(self._on_next)
self.buttons.append(self.next_button)
self.abort_button = QPushButton(_("Cancel"))
self.abort_button.clicked.connect(self._on_cancel)
self.buttons.append(self.abort_button)
self.vbox.addLayout(Buttons(*self.buttons))
def _on_cancel(self):
self.on_cancel()
self.parent.close()
def _on_next(self):
if self.validate():
self.on_next()
def _on_previous(self):
self.on_previous()
def get_content(self):
pass
def validate(self):
return True
class BalWizardHeirsWidget(BalWizardWidget):
title="Bitcoin After Life Heirs"
message="Please add your heirs\n remember that 100% of wallet balance will be spent"
def get_content(self):
self.heirs_list=HeirList(self.bal_window,self.parent)
button_add=QPushButton(_("Add"))
button_add.clicked.connect(self.add_heir)
button_import=QPushButton(_("Import from file"))
button_import.clicked.connect(self.import_from_file)
widget=QWidget()
vbox=QVBoxLayout(widget)
vbox.addWidget(self.heirs_list)
vbox.addLayout(Buttons(button_add,button_import))
return widget
def import_from_file(self):
self.bal_window.import_heirs()
self.heirs_list.update()
def add_heir(self):
self.bal_window.new_heir_dialog()
self.heirs_list.update()
def validate(self):
return True
class BalWizardWEDownloadWidget(BalWizardWidget):
title=_("Bitcoin After Life Will-Executors")
message=_("Choose willexecutors download method")
def get_content(self):
question=QLabel()
self.combo=QComboBox()
self.combo.addItems([
"Automatically download and select willexecutors",
"Only download willexecutors list",
"Import willexecutor list from file",
"Manual"])
#heir_name.setFixedWidth(32 * char_width_in_lineedit())
return self.combo
def validate(self):
return True
def _on_next(self):
index = self.combo.currentIndex()
_logger.debug(f"selected index:{index}")
if index < 2:
willexecutors = Willexecutors.download_list(self.bal_window.bal_plugin)
if index < 1:
for we in willexecutors.values():
we['selected']=True
elif index == 2:
#TODO import from file
pass
elif index == 3:
#TODO DO NOTHING
pass
if self.validate():
return self.on_next()
class BalWizardWEWidget(BalWizardWidget):
title=("Bitcoin After Life Will-Executors")
message=_("Configure and select your willexecutors")
def get_content(self):
widget=QWidget()
vbox=QVBoxLayout(widget)
vbox.addWidget(WillExecutorWidget(self,self.bal_window,Willexecutors.get_willexecutors(self.bal_window.bal_plugin)))
return widget
class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
title=("Bitcoin After Life Will Settings")
message=_("")
def get_content(self):
widget=QWidget()
self.heir_locktime = HeirsLockTimeEdit(widget,0)
will_settings=self.bal_window.bal_plugin.WILL_SETTINGS.get()
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.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y"
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
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.will_settings['threshold'] = self.heir_threshold.get_locktime()
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
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['tx_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['tx_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))
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")))
spacer_widget = QWidget()
spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
layout.addWidget(spacer_widget)
return widget
class BalWaitingDialog(BalDialog):
updatemessage=pyqtSignal([str], arguments=['message'])
def __init__(self, bal_window: 'BalWindow', message: str, task, on_success=None, on_error=None, on_cancel=None,exe=True):
@ -1511,25 +1779,27 @@ class BalBlockingWaitingDialog(BalDialog):
self.accept()
class bal_checkbox(QCheckBox):
def __init__(self, plugin,variable,window=None):
def __init__(self, variable,window=None):
QCheckBox.__init__(self)
self.setChecked(plugin.config_get(variable))
self.setChecked(variable.get())
def on_check(v):
plugin.config.set_key(variable, v == 2)
plugin.config_get(variable)
variable.set(v == 2)
variable.get()
self.stateChanged.connect(on_check)
class BalCloseDialog(BalDialog):
class BalBuildWillDialog(BalDialog):
updatemessage=pyqtSignal()
def __init__(self,bal_window):
BalDialog.__init__(self,bal_window.window,"Closing BAL")
def __init__(self,bal_window,parent=None):
if not parent:
parent = bal_window.window
BalDialog.__init__(self,parent,"Building Will")
self.updatemessage.connect(self.update)
self.bal_window=bal_window
self.message_label = QLabel("Closing BAL:")
self.message_label = QLabel("Building Will:")
self.vbox = QVBoxLayout(self)
self.vbox.addWidget(self.message_label)
self.qwidget=QWidget()
@ -1547,8 +1817,8 @@ class BalCloseDialog(BalDialog):
def task_finished(self):
pass
def close_plugin_task(self):
_logger.debug("close task to be started")
def build_will_task(self):
_logger.debug("build will task to be started")
self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1)
self.show()
self.exec()
@ -1713,7 +1983,7 @@ class BalCloseDialog(BalDialog):
self.msg_set_invalidating()
#need to sign invalidate and restart phase 1
password = self.bal_window.get_wallet_password("Invalidate your old will",parent=self.bal_window.window)
password = self.bal_window.get_wallet_password("Invalidate your old will",parent=self)
if password is False:
self.msg_set_invalidating("Aborted")
self.wait(3)
@ -1724,7 +1994,7 @@ class BalCloseDialog(BalDialog):
return
elif self.have_to_sign:
password = self.bal_window.get_wallet_password("Sign your will",parent=self.bal_window.window)
password = self.bal_window.get_wallet_password("Sign your will",parent=self)
if password is False:
self.msg_set_signing('Aborted')
else:
@ -1739,6 +2009,7 @@ class BalCloseDialog(BalDialog):
self.close()
def closeEvent(self,event):
self.bal_window.update_all()
self._stopping=True
self.thread.stop()
@ -1868,9 +2139,9 @@ class HeirList(MyTreeView,MessageBoxMixin):
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 1001
key_role = ROLE_HEIR_KEY
def __init__(self, bal_window: 'BalWindow'):
def __init__(self, bal_window: 'BalWindow',parent):
super().__init__(
parent=bal_window.window,
parent=parent,
main_window=bal_window.window,
stretch_column=self.Columns.NAME,
editable_columns=[self.Columns.NAME,self.Columns.ADDRESS,self.Columns.AMOUNT],
@ -1939,6 +2210,12 @@ class HeirList(MyTreeView,MessageBoxMixin):
menu.addAction(_("Delete"), lambda: self.bal_window.delete_heirs(selected_keys))
menu.exec(self.viewport().mapToGlobal(position))
#def get_selected_keys(self):
# selected_keys = []
# for s_idx in self.selected_in_column(self.Columns.NAME):
# sel_key = self.model().itemFromIndex(s_idx).data(0)
# selected_keys.append(sel_key)
# return selected_keys
def update(self):
if self.maybe_defer_update():
return
@ -1970,6 +2247,7 @@ class HeirList(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
@ -1984,12 +2262,12 @@ class HeirList(MyTreeView,MessageBoxMixin):
menu.addAction(_("Import"), self.bal_window.import_heirs)
menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
self.heir_locktime = HeirsLockTimeEdit(self.window(),0)
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.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y"
self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.heir_locktime.valueEdited.connect(on_heir_locktime)
self.heir_threshold = HeirsLockTimeEdit(self,0)
@ -1998,7 +2276,7 @@ class HeirList(MyTreeView,MessageBoxMixin):
self.heir_threshold.set_locktime('180d')
self.bal_window.will_settings['threshold'] = self.heir_threshold.get_locktime()
self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.heir_threshold.valueEdited.connect(on_heir_threshold)
self.heir_tx_fees = QSpinBox()
@ -2008,7 +2286,7 @@ class HeirList(MyTreeView,MessageBoxMixin):
if not self.heir_tx_fees.value():
self.heir_tx_fees.set_value(1)
self.bal_window.will_settings['tx_fees'] = self.heir_tx_fees.value()
self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
@ -2047,16 +2325,18 @@ class HeirList(MyTreeView,MessageBoxMixin):
return toolbar
def update_will_settings(self):
self.heir_threshold.set_locktime(self.bal_window.will_settings['threshold'])
try:
self.heir_locktime.set_locktime(self.bal_window.will_settings['locktime'])
self.heir_tx_fees.setValue(int(self.bal_window.will_settings['tx_fees']))
self.heir_threshold.set_locktime(self.bal_window.will_settings['threshold'])
except Exception as e:
print("Exception update_will_settings",e)
def build_transactions(self):
will = self.bal_window.prepare_will()
class PreviewList(MyTreeView):
class Columns(MyTreeView.BaseColumnsEnum):
LOCKTIME = enum.auto()
@ -2074,12 +2354,14 @@ class PreviewList(MyTreeView):
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000
key_role = ROLE_HEIR_KEY
def __init__(self, parent: 'BalWindow',will):
def __init__(self, bal_window: 'BalWindow',parent,will):
super().__init__(
parent=parent.window,
parent=parent,
stretch_column=self.Columns.TXID,
)
self.decimal_point=parent.bal_plugin.config.get_decimal_point
self.parent=parent
self.bal_window=bal_window
self.decimal_point=bal_window.bal_plugin.config.get_decimal_point
self.setModel(QStandardItemModel(self))
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
@ -2087,14 +2369,13 @@ class PreviewList(MyTreeView):
if not will is None:
self.will = will
else:
self.will = parent.willitems
self.will = bal_window.willitems
self.bal_window = parent
self.wallet=parent.window.wallet
self.wallet=bal_window.window.wallet
self.setModel(QStandardItemModel(self))
self.setSortingEnabled(True)
self.std_model = self.model()
self.config = parent.bal_plugin.config
self.config = bal_window.bal_plugin.config
self.bal_plugin=self.bal_window.bal_plugin
self.update()
@ -2120,6 +2401,7 @@ class PreviewList(MyTreeView):
menu.exec(self.viewport().mapToGlobal(position))
def delete(self,selected_keys):
for key in selected_keys:
del self.will[key]
@ -2222,6 +2504,10 @@ class PreviewList(MyTreeView):
menu.addAction(_("Broadcast"), self.broadcast)
menu.addAction(_("Check"), self.check)
menu.addAction(_("Invalidate"), self.invalidate_will)
wizard=QPushButton(_("Setup Wizard"))
wizard.clicked.connect(self.bal_window.init_wizard)
prepareButton = QPushButton(_("Prepare"))
prepareButton.clicked.connect(self.build_transactions)
signButton = QPushButton(_("Sign"))
@ -2232,6 +2518,7 @@ class PreviewList(MyTreeView):
displayButton.clicked.connect(self.bal_window.preview_modal_dialog)
hlayout = QHBoxLayout()
widget = QWidget()
hlayout.addWidget(wizard)
hlayout.addWidget(prepareButton)
hlayout.addWidget(signButton)
hlayout.addWidget(pushButton)
@ -2358,9 +2645,8 @@ def read_bal_QPixmap(icon_basename: str=DEFAULT_ICON) -> QPixmap:
return QPixmap(icon_path(icon_basename))
class WillDetailDialog(BalDialog):
def __init__(self, bal_window):
self.will = bal_window.willitems
@ -2584,8 +2870,10 @@ class WillExecutorList(MyTreeView):
for k in selected_keys:
wout[k]=self.parent.willexecutors_list[k]
self.parent.update_willexecutors(wout)
self.update()
def get_edit_key_from_coordinate(self, row, col):
a= self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col)
return a
@ -2625,6 +2913,7 @@ class WillExecutorList(MyTreeView):
pass
def update(self):
if self.parent.willexecutors_list is None:
return
try:
@ -2676,27 +2965,41 @@ class WillExecutorList(MyTreeView):
idx = self.model().index(row_count, self.Columns.NAME)
set_current = QPersistentModelIndex(idx)
self.set_current_idx(set_current)
self.parent.save_willexecutors()
except Exception as e:
_logger.error(e)
class WillExecutorDialog(BalDialog,MessageBoxMixin):
def __init__(self, bal_window):
BalDialog.__init__(self,bal_window.window)
self.bal_plugin = bal_window.bal_plugin
self.config = self.bal_plugin.config
self.window = bal_window.window
self.bal_window = bal_window
class WillExecutorWidget(QWidget,MessageBoxMixin):
def __init__(self,parent,bal_window,willexecutors=None):
self.bal_window=bal_window
self.bal_plugin=bal_window.bal_plugin
self.parent=parent
MessageBoxMixin.__init__(self)
QWidget.__init__(self,parent)
if willexecutors:
self.willexecutors_list=willexecutors
else:
self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin)
self.setWindowTitle(_('Will-Executor Service List'))
self.setMinimumSize(1000, 200)
self.size_label = QLabel()
self.willexecutor_list = WillExecutorList(self)
vbox = QVBoxLayout(self)
vbox.addWidget(self.size_label)
widget = QWidget()
hbox=QHBoxLayout(widget)
hbox.addWidget(QLabel(_("Add transactions without willexecutor")))
heir_no_willexecutor = bal_checkbox(self.bal_plugin.NO_WILLEXECUTOR)
hbox.addWidget(heir_no_willexecutor)
spacer_widget = QWidget()
spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
hbox.addWidget(spacer_widget)
vbox.addWidget(widget)
vbox.addWidget(self.willexecutor_list)
buttonbox = QHBoxLayout()
@ -2720,14 +3023,8 @@ class WillExecutorDialog(BalDialog,MessageBoxMixin):
b.clicked.connect(self.add)
buttonbox.addWidget(b)
b = QPushButton(_('Close'))
b.clicked.connect(self.close)
buttonbox.addWidget(b)
vbox.addLayout(buttonbox)
self.willexecutor_list.update()
def add(self):
self.willexecutors_list["http://localhost:8080"]={"info":"New Will Executor","base_fee":0,"status":"-1"}
self.willexecutor_list.update()
@ -2735,13 +3032,13 @@ class WillExecutorDialog(BalDialog,MessageBoxMixin):
self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin) )
self.willexecutor_list.update()
def export_file(self, path):
Util.export_meta_gui(self, _('willexecutors.json'), self.export_json_file)
Util.export_meta_gui(self.bal_window.window, _('willexecutors.json'), self.export_json_file)
def export_json_file(self,path):
write_json_file(path, self.willexecutors_list)
def import_file(self):
import_meta_gui(self, _('willexecutors.json'), self.import_json_file, self.willexecutors_list.update)
import_meta_gui(self.bal_window.window, _('willexecutors.json'), self.import_json_file, self.willexecutors_list.update)
def update_willexecutors(self,wes=None):
if not wes:
@ -2762,6 +3059,26 @@ class WillExecutorDialog(BalDialog,MessageBoxMixin):
def _validate(self,data):
return data
def save_willexecutors(self):
Willexecutors.save(self.bal_window.bal_plugin, self.willexecutors_list)
class WillExecutorDialog(BalDialog,MessageBoxMixin):
def __init__(self, bal_window):
BalDialog.__init__(self,bal_window.window)
self.bal_plugin = bal_window.bal_plugin
self.config = self.bal_plugin.config
self.window = bal_window.window
self.bal_window = bal_window
self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin)
self.setWindowTitle(_('Will-Executor Service List'))
self.setMinimumSize(1000, 200)
vbox = QVBoxLayout(self)
vbox.addWidget(WillExecutorWidget(self,self.bal_window,self.willexecutors_list))
def is_hidden(self):
return self.isMinimized() or self.isHidden()
@ -2778,6 +3095,4 @@ class WillExecutorDialog(BalDialog,MessageBoxMixin):
def closeEvent(self, event):
event.accept()
def save_willexecutors(self):
self.bal_plugin.config.set_key(self.bal_plugin.WILLEXECUTORS,self.willexecutors_list,save=True)

View File

@ -319,6 +319,7 @@ class Will:
locktime = Util.get_current_height(wallet.network)
tx = PartialTransaction.from_io(utxo_to_spend, [out], locktime=locktime, version=2)
tx.set_rbf(True)
print("fees_per_byte:",fees_per_byte)
fee=tx.estimated_size()*fees_per_byte
if balance -fee >0:
out = PartialTxOutput.from_address_and_value(change_addresses[0],balance - fee)
@ -329,7 +330,7 @@ class Will:
return tx
else:
_logger.debug("balance - fee <=0")
_logger.debug(f"balance({balance}) - fee({fee}) <=0")
pass
else:
_logger.debug("len utxo_to_spend <=0")

View File

@ -8,39 +8,49 @@ from electrum import constants
from electrum.logging import get_logger
from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _
from .bal import BalPlugin
from .util import Util
DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__)
class Willexecutors:
def save(bal_plugin, willexecutors):
aw=bal_plugin.WILLEXECUTORS.get()
aw[constants.net.NET_NAME]=willexecutors
print("save",aw)
bal_plugin.WILLEXECUTORS.set(aw)
def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
willexecutors = bal_plugin.config_get(bal_plugin.WILLEXECUTORS)
willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors=willexecutors.get(constants.net.NET_NAME,{})
for w in willexecutors:
Willexecutors.initialize_willexecutor(willexecutors[w],w)
bal=bal_plugin.DEFAULT_SETTINGS[bal_plugin.WILLEXECUTORS]
bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME,{})
for bal_url,bal_executor in bal.items():
if not bal_url in willexecutors:
_logger.debug("replace bal")
willexecutors[bal_url]=bal_executor
_logger.debug(f"force add {bal_url} willexecutor")
willexecutors[bal_url] = bal_executor
if update:
found = False
for url,we in willexecutors.items():
if Willexecutors.is_selected(we):
found = True
if found or force:
if bal_plugin.config_get(bal_plugin.PING_WILLEXECUTORS) or force:
if bal_plugin.PING_WILLEXECUTORS.get() or force:
ping_willexecutors = True
if bal_plugin.config_get(bal_plugin.ASK_PING_WILLEXECUTORS) and not force:
if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force:
if bal_window:
ping_willexecutors = bal_window.window.question(_("Contact willexecutors servers to update payment informations?"))
if ping_willexecutors:
if task:
bal_window.ping_willexecutors(willexecutors)
else:
bal_window.ping_willexecutors_task(willexecutors)
return willexecutors
w_sorted = dict(sorted(willexecutors.items(), key=lambda w:w[1].get('sort',0),reverse=True))
return w_sorted
def is_selected(willexecutor,value=None):
if not willexecutor:
return False
@ -90,7 +100,7 @@ class Willexecutors:
raise ErrorConnectingServer('You are offline.')
_logger.debug(f'<-- {method} {url} {data}')
headers = {}
headers['user-agent'] = 'BalPlugin'
headers['user-agent'] = f"BalPlugin v:{BalPlugin.version()}"
headers['Content-Type']='text/plain'
try:
@ -182,6 +192,7 @@ class Willexecutors:
if not status is None:
willexecutor['status'] = status
willexecutor['selected'] = Willexecutors.is_selected(willexecutor,selected)
def download_list(bal_plugin):
try:
l = Willexecutors.send_request('get',"https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100")
@ -189,8 +200,10 @@ class Willexecutors:
for w in l:
willexecutor=l[w]
Willexecutors.initialize_willexecutor(willexecutor,w,'New',False)
bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
#bal_plugin.WILLEXECUTORS.set(l)
#bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
return l
except Exception as e:
_logger.error(f"error downloading willexecutors list:{e}")
return {}
@ -201,7 +214,7 @@ class Willexecutors:
for w in willexecutors:
willexecutor=willexecutors[w]
Willexecutors.initialize_willexecutor(willexecutor,w,'New',False)
bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,willexecutors,save=True)
#bal_plugin.WILLEXECUTORS.set(willexecutors)
return h
except Exception as e:
_logger.error(f"error opening willexecutors json: {e}")
@ -216,3 +229,22 @@ class Willexecutors:
except Exception as e:
_logger.error(f"error contacting {url} for checking txs {e}")
raise e
class WillExecutor:
def __init__(self,url,base_fee,chain,info,version):
self.url = url
self.base_fee = base_fee
self.chain = chain
self.info = info
self.version = version
def from_dict(d):
we = WillExecutor(
d['url'],
d['base_fee'],
d['chain'],
d['info'],
d['version']
)