8 Commits

6 changed files with 180 additions and 93 deletions

4
bal.py
View File

@@ -48,7 +48,7 @@ class BalConfig:
class BalPlugin(BasePlugin): class BalPlugin(BasePlugin):
_version=None _version=None
__version__ = "0.2.7" #AUTOMATICALLY GENERATED DO NOT EDIT __version__ = "0.2.8" #AUTOMATICALLY GENERATED DO NOT EDIT
def version(self): def version(self):
if not self._version: if not self._version:
try: try:
@@ -149,6 +149,8 @@ class BalPlugin(BasePlugin):
self.HIDE_REPLACED.set(self._hide_replaced) self.HIDE_REPLACED.set(self._hide_replaced)
def validate_will_settings(self, will_settings): def validate_will_settings(self, will_settings):
if not will_settings:
will_settings=[]
if int(will_settings.get("baltx_fees", 1)) < 1: if int(will_settings.get("baltx_fees", 1)) < 1:
will_settings["baltx_fees"] = 1 will_settings["baltx_fees"] = 1
if not will_settings.get("threshold"): if not will_settings.get("threshold"):

View File

@@ -30,9 +30,11 @@ from electrum.transaction import (
PartialTransaction, PartialTransaction,
PartialTxInput, PartialTxInput,
PartialTxOutput, PartialTxOutput,
TxOutput,
TxOutpoint, TxOutpoint,
# TxOutput, # TxOutput,
) )
from electrum.payment_identifier import PaymentIdentifier
from electrum.util import ( from electrum.util import (
bfh, bfh,
read_json_file, read_json_file,
@@ -44,7 +46,6 @@ from electrum.util import (
from .util import Util from .util import Util
from .willexecutors import Willexecutors from .willexecutors import Willexecutors
from electrum.util import BitcoinException from electrum.util import BitcoinException
if TYPE_CHECKING: if TYPE_CHECKING:
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
@@ -71,28 +72,22 @@ def reduce_outputs(in_amount, out_amount, fee, outputs):
output.value = math.floor((in_amount - fee) / out_amount * output.value) output.value = math.floor((in_amount - fee) / out_amount * output.value)
""" def create_op_return_script(data_hex: str) -> bytes:
#TODO: put this method inside wallet.db to replace or complete get_locktime_for_new_transaction """Crea scriptpubkey OP_RETURN in bytes"""
def get_current_height(network:'Network'): data = bytes.fromhex(data_hex)
#if no network or not up to date, just set locktime to zero
if not network:
return 0
chain = network.blockchain()
if chain.is_tip_stale():
return 0
# figure out current block height
chain_height = chain.height() # learnt from all connected servers, SPV-checked
server_height = network.get_server_height() # height claimed by main server, unverified
# note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
# - if it's lagging too much, it is the network's job to switch away
if server_height < chain_height - 10:
# the diff is suspiciously large... give up and use something non-fingerprintable
return 0
# discourage "fee sniping"
height = min(chain_height, server_height)
return height
"""
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
script = b'\x6a' + bytes([len(data)]) + data
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): def prepare_transactions(locktimes, available_utxos, fees, wallet):
available_utxos = sorted( available_utxos = sorted(
@@ -167,6 +162,13 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
outputs.append(change) outputs.append(change)
for i in range(0, 100): for i in range(0, 100):
random.shuffle(outputs) 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)
#outputs.append(PartialTxOutput(value=0, scriptpubkey=op_return_script))
tx = PartialTransaction.from_io( tx = PartialTransaction.from_io(
used_utxos, used_utxos,
outputs, outputs,

View File

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

196
qt.py
View File

@@ -404,9 +404,13 @@ class BalWindow(Logger):
self.bal_plugin.get_decimal_point = self.window.get_decimal_point self.bal_plugin.get_decimal_point = self.window.get_decimal_point
if self.window.wallet: if self.window.wallet:
self.wallet = self.window.wallet
if not self.will_settings:
self.will_settings = self.bal_plugin.WILL_SETTINGS.get()
Util.fix_will_settings_tx_fees(self.will_settings)
self.heirs_tab = self.create_heirs_tab() self.heirs_tab = self.create_heirs_tab()
self.will_tab = self.create_will_tab() self.will_tab = self.create_will_tab()
self.wallet = self.window.wallet
self.heirs_tab.wallet = self.wallet self.heirs_tab.wallet = self.wallet
self.will_tab.wallet = self.wallet self.will_tab.wallet = self.wallet
@@ -488,17 +492,17 @@ class BalWindow(Logger):
self.close_wallet() self.close_wallet()
return return
if not self.will_settings: #if not self.will_settings:
self.will_settings = self.wallet.db.get_dict("will_settings") # self.will_settings = self.wallet.db.get_dict("will_settings")
Util.fix_will_settings_tx_fees(self.will_settings) # Util.fix_will_settings_tx_fees(self.will_settings)
self.logger.info("will_settings: {}".format(self.will_settings)) # self.logger.info("will_settings: {}".format(self.will_settings))
if not self.will_settings: # if not self.will_settings:
Util.copy(self.will_settings, self.bal_plugin.default_will_settings()) # Util.copy(self.will_settings, self.bal_plugin.default_will_settings())
self.logger.debug("not_will_settings {}".format(self.will_settings)) # self.logger.debug("not_will_settings {}".format(self.will_settings))
self.bal_plugin.validate_will_settings(self.will_settings) # self.bal_plugin.validate_will_settings(self.will_settings)
self.heir_list_widget.update_will_settings() # self.heir_list_widget.update_will_settings()
self.heir_list_widget.update() # self.heir_list_widget.update()
def init_wizard(self): def init_wizard(self):
wizard_dialog = BalWizardDialog(self) wizard_dialog = BalWizardDialog(self)
@@ -732,6 +736,35 @@ class BalWindow(Logger):
def show_critical(self, text): def show_critical(self, text):
self.window.show_critical(text) self.window.show_critical(text)
def update_locktime_widgets(self,locktime):
locktime = self.will_settings["locktime"] = (
locktime
if locktime
else "1y"
)
self.bal_plugin.WILL_SETTINGS.set(self.will_settings)
try:
self.heir_list_widget.heir_locktime.set_locktime(locktime)
except Exception as e:
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 as e:
pass
try:
self.will_list.heir_threshold.set_locktime(threshold)
except Exception as e:
pass
def init_heirs_to_locktime(self, multiverse=False): def init_heirs_to_locktime(self, multiverse=False):
for heir in self.heirs: for heir in self.heirs:
h = self.heirs[heir] h = self.heirs[heir]
@@ -1318,8 +1351,8 @@ class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
w.setEnabled(False) w.setEnabled(False)
prev_locktime = self.editor.get_locktime() prev_locktime = self.editor.get_locktime()
self.editor = self.option_index_to_editor_map[i] self.editor = self.option_index_to_editor_map[i]
if self.editor.is_acceptable_locktime(prev_locktime): #if self.editor.is_acceptable_locktime(prev_locktime):
self.editor.set_locktime(prev_locktime, force=True) # self.editor.set_locktime(prev_locktime, force=False)
self.editor.setVisible(True) self.editor.setVisible(True)
self.editor.setEnabled(True) self.editor.setEnabled(True)
@@ -1330,8 +1363,16 @@ class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
self.combo.setCurrentIndex(index) self.combo.setCurrentIndex(index)
self.on_current_index_changed(index) self.on_current_index_changed(index)
def set_locktime(self, x: Any, force=True) -> None: def set_locktime(self, x: Any, force=None) -> None:
self.editor.set_locktime(x, force) if force is None:
force=True
try:
int(x)
self.set_index(1)
except:
if isinstance(x,str):
self.set_index(0)
self.editor.set_locktime(x, force=False)
class LockTimeRawEdit(QLineEdit, _LockTimeEditor): class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
@@ -1859,19 +1900,14 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
def get_content(self): def get_content(self):
widget = QWidget() widget = QWidget()
self.heir_locktime = HeirsLockTimeEdit(widget, 0) self.heir_locktime = HeirsLockTimeEdit(widget, 0)
will_settings = self.bal_window.bal_plugin.WILL_SETTINGS.get() #will_settings = self.bal_window.bal_plugin.WILL_SETTINGS.get()
will_settings = self.bal_window.will_settings will_settings = self.bal_window.will_settings
self.heir_locktime.set_locktime(will_settings["locktime"]) self.heir_locktime.set_locktime(will_settings["locktime"])
def on_heir_locktime(): def on_heir_locktime():
if not self.heir_locktime.get_locktime(): if not self.heir_locktime.get_locktime():
self.heir_locktime.set_locktime("1y") self.heir_locktime.set_locktime("1y")
self.bal_window.will_settings["locktime"] = ( self.bal_window.update_locktime_widgets(self.heir_locktime.get_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_locktime.valueEdited.connect(on_heir_locktime)
@@ -1881,11 +1917,7 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
def on_heir_threshold(): def on_heir_threshold():
if not self.heir_threshold.get_locktime(): if not self.heir_threshold.get_locktime():
self.heir_threshold.set_locktime("180d") self.heir_threshold.set_locktime("180d")
self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
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_threshold.valueEdited.connect(on_heir_threshold)
@@ -2143,15 +2175,12 @@ class BalBuildWillDialog(BalDialog):
_logger.debug("variables ok") _logger.debug("variables ok")
self.msg_set_status("checking variables:", varrow, "Ok", self.COLOR_OK) self.msg_set_status("checking variables:", varrow, "Ok", self.COLOR_OK)
except AmountException: except AmountException:
self.msg_set_status( self.msg_set_checking(
"checking variables", self.msg_warning(
varrow,
_(
"In the inheritance process, " "In the inheritance process, "
+ "the entire wallet will always be fully emptied. \n" + "the entire wallet will always be fully emptied. \n"
+ "Your settings require an adjustment of the amounts" + "Your settings require an adjustment of the amounts"
), )
self.COLOR_WARNING,
) )
self.msg_set_checking() self.msg_set_checking()
@@ -2526,21 +2555,35 @@ class BalBuildWillDialog(BalDialog):
pass pass
self.updatemessage.emit() self.updatemessage.emit()
def clear_layout(self,layout): #def clear_layout(self,layout):
while layout.count(): # while layout.count():
item = layout.takeAt(0) # item = layout.takeAt(0)
w = item.widget() # w = item.widget()
if w: # if w:
w.setParent(None) # w.setParent(None)
w.deleteLater() # w.deleteLater()
#def msg_update(self):
# self.clear_layout(self.labelsbox)
# for label in self.labels:
# label=label.replace("\n","<br>")
# qlabel=QLabel(label)
# qlabel.setWordWrap(True)
# self.labelsbox.addWidget(qlabel)
# self.labelsbox.activate()
# self.qwidget.setMinimumSize(self.labelsbox.sizeHint())
# self.qwidget.adjustSize()
# from PyQt6.QtWidgets import QApplication
# QApplication.processEvents()
#
# self.adjustSize()
def msg_update(self): def msg_update(self):
self.clear_layout(self.labelsbox) full_text = "<br><br>".join(self.labels).replace("\n", "<br>")
for label in self.labels: self.message_label.setText(full_text)
label=label.replace("\n","<br>") self.message_label.adjustSize()
qlabel=QLabel(label) #self.setMinimumHeight(len(self.labels)*40)
self.labelsbox.addWidget(QLabel(label),1) self.resize(self.sizeHint())
self.setMinimumHeight(30*(len(self.labels)+2))
def get_text(self): def get_text(self):
@@ -2728,32 +2771,28 @@ class HeirListWidget(MyTreeView, MessageBoxMixin):
menu.addAction(_("Import"), self.bal_window.import_heirs) menu.addAction(_("Import"), self.bal_window.import_heirs)
menu.addAction(_("Export"), lambda: self.bal_window.export_heirs()) menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
self.heir_locktime = HeirsLockTimeEdit(self, 0) 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']
self.heir_locktime = HeirsLockTimeEdit(self, 0)
def on_heir_locktime(): def on_heir_locktime():
if not self.heir_locktime.get_locktime(): if not self.heir_locktime.get_locktime():
self.heir_locktime.set_locktime("1y") self.heir_locktime.set_locktime("1y")
self.bal_window.will_settings["locktime"] = ( self.bal_window.update_locktime_widgets(self.heir_locktime.get_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_locktime.valueEdited.connect(on_heir_locktime)
self.heir_locktime.set_locktime(locktime)
self.heir_threshold = HeirsLockTimeEdit(self, 0) self.heir_threshold = HeirsLockTimeEdit(self, 0)
def on_heir_threshold(): def on_heir_threshold():
if not self.heir_threshold.get_locktime(): if not self.heir_threshold.get_locktime():
self.heir_threshold.set_locktime("180d") self.heir_threshold.set_locktime("180d")
self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
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_threshold.valueEdited.connect(on_heir_threshold)
self.heir_threshold.set_locktime(threshold)
self.heir_tx_fees = QSpinBox() self.heir_tx_fees = QSpinBox()
self.heir_tx_fees.setMinimum(1) self.heir_tx_fees.setMinimum(1)
@@ -3038,6 +3077,21 @@ class PreviewList(MyTreeView):
menu.addAction(_("Check"), self.check) menu.addAction(_("Check"), self.check)
menu.addAction(_("Invalidate"), self.invalidate_will) 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 = QPushButton(_("Setup Wizard"))
wizard.clicked.connect(self.bal_window.init_wizard) wizard.clicked.connect(self.bal_window.init_wizard)
#display = QPushButton(_("Display")) #display = QPushButton(_("Display"))
@@ -3048,6 +3102,32 @@ class PreviewList(MyTreeView):
widget = QWidget() widget = QWidget()
hlayout = QHBoxLayout(widget) hlayout = QHBoxLayout(widget)
hlayout.addWidget(QLabel(_("Check Alive:")))
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(wizard) hlayout.addWidget(wizard)
hlayout.addWidget(refresh) hlayout.addWidget(refresh)
toolbar.insertWidget(2, widget) toolbar.insertWidget(2, widget)

13
util.py
View File

@@ -494,3 +494,16 @@ class Util:
del will[txid]["tx_fees"] del will[txid]["tx_fees"]
have_to_update = True have_to_update = True
return have_to_update return have_to_update
def text_to_hex(text: str) -> str:
"""Convert text to hexadecimal string"""
hex_string = text.encode('utf-8').hex()
return hex_string
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"

10
will.py
View File

@@ -148,16 +148,6 @@ class Will:
inp._TxInput__value_sats = change.value inp._TxInput__value_sats = change.value
return inp return inp
"""
in questa situazione sono presenti due transazioni con id differente(quindi transazioni differenti)
per prima cosa controllo il locktime
se il locktime della nuova transazione e' maggiore del locktime della vecchia transazione, allora
confronto gli eredi, per locktime se corrispondono controllo i willexecutor
se hanno la stessa url ma le fee vecchie sono superiori alle fee nuove, allora anticipare.
"""
def check_anticipate(ow: "WillItem", nw: "WillItem"): def check_anticipate(ow: "WillItem", nw: "WillItem"):
anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1) anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1)
if int(nw.tx.locktime) >= int(anticipate): if int(nw.tx.locktime) >= int(anticipate):