diff --git a/VERSION b/VERSION index e4a15f8..dabaed9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.2a +0.2.2b diff --git a/bal.py b/bal.py index e24308e..183a49b 100644 --- a/bal.py +++ b/bal.py @@ -7,11 +7,16 @@ from electrum import json_db from electrum.transaction import tx_from_any 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) +def get_will_settings(x): + print(x) +json_db.register_dict('heirs', tuple, None) +json_db.register_dict('will', dict,None) +json_db.register_dict('will_settings', lambda x:x,None) +#{'rubiconda': ['bcrt1qgv0wu4v6kjzef5mnxfh2m9z6y7mez0ja0tt8mu', '45%', '1y'], 'veronica': ['bcrt1q6vxuvwrt8x5c9u9u29y5uq7frscr0vgc2dy60j', '15%', '1y']} from electrum.logging import get_logger +def get_will_settings(x): + print(x) def get_will(x): try: @@ -22,6 +27,7 @@ def get_will(x): class BalConfig(): def __init__(self, config, name, default): + print("init bal_config") self.config = config self.name = name self.default = default @@ -53,6 +59,7 @@ class BalPlugin(BasePlugin): SIZE = (159, 97) def __init__(self, parent, config, name): + print("init bal_plugin") self.logger = get_logger(__name__) BasePlugin.__init__(self, parent, config, name) self.base_dir = os.path.join(config.electrum_path(), 'bal') @@ -102,7 +109,7 @@ class BalPlugin(BasePlugin): } }) self.WILL_SETTINGS = BalConfig(config, "bal_will_settings", { - 'tx_fees':100, + 'baltx_fees':100, 'threshold':'180d', 'locktime':'1y', }) @@ -124,11 +131,19 @@ class BalPlugin(BasePlugin): self.HIDE_REPLACED.set(self._hide_replaced) def validate_will_settings(self,will_settings): - if int(will_settings.get('tx_fees',1))<1: - will_settings['tx_fees']=1 + print(type(will_settings)) + print(will_settings.get('baltx_fees',1),1) + if int(will_settings.get('baltx_fees',1))<1: + will_settings['baltx_fees']=1 if not will_settings.get('threshold'): will_settings['threshold']='180d' if not will_settings.get('locktime')=='': will_settings['locktime']='1y' return will_settings + def default_will_settings(self): + return { + 'baltx_fees':100, + 'threshold':'180d', + 'locktime':'1y' + } diff --git a/bal_wallet_utils.py b/bal_wallet_utils.py new file mode 100755 index 0000000..6cf9dda --- /dev/null +++ b/bal_wallet_utils.py @@ -0,0 +1,61 @@ +#!env/bin/python3 +#same as qt but for command line, useful if you are going to fix various wallet +#also easier to read the code +from electrum.storage import WalletStorage +from electrum.util import MyEncoder +import json +import sys +import getpass +import os + +default_fees= 100 + +def fix_will_settings_tx_fees(json_wallet): + tx_fees = json_wallet.get('will_settings',{}).get('tx_fees',False) + if tx_fees: + json_wallet['will_settings']['baltx_fees']=json_wallet.get('will_settings',{}).get('tx_fees',default_fees) + del json_wallet['will_settings']['tx_fees'] + return True + return False + +def uninstall_bal(json_wallet): + del json_wallet['will_settings'] + del json_wallet['will'] + del json_wallet['heirs'] + return True + +def save(json_wallet,storage): + human_readable=not storage.is_encrypted() + storage.write(json.dumps( + json_wallet, + indent=4 if human_readable else None, + sort_keys=bool(human_readable), + cls=MyEncoder, + )) + +if __name__ == '__main__': + if len(sys.argv) <3: + print("usage: ./bal_wallet_utils ") + print("available commands: uninstall, fix") + exit(1) + if not os.path.exists(sys.argv[2]): + print("Error: wallet not found") + exit(1) + command = sys.argv[1] + path = sys.argv[2] + + storage=WalletStorage(path) + if storage.is_encrypted(): + password = getpass.getpass("Enter wallet password: ", stream = None) + storage.decrypt(password) + data=storage.read() + json_wallet=json.loads(data) + have_to_save=False + if command == 'fix': + have_to_save = fix_will_settings_tx_fees(json_wallet) + if command == 'uninstall': + have_to_save = uninstall_bal(json_wallet) + if have_to_save: + save(json_wallet,storage) + else: + print("nothing to do") diff --git a/bal_wallet_utils_qt.py b/bal_wallet_utils_qt.py new file mode 100755 index 0000000..cea7cfb --- /dev/null +++ b/bal_wallet_utils_qt.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +#this script will help to fix tx_fees wallet bug +#also added an uninstall button to remove any bal data from wallet +#still very work in progress. +# +#have to be executed from electrum source code directory in example /home/user/projects/electrum/ +# +import sys +import os +import json +from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, + QLabel, QLineEdit, QPushButton, QWidget, QFileDialog, + QGroupBox, QTextEdit) +from PyQt6.QtCore import Qt +from electrum.storage import WalletStorage +from electrum.util import MyEncoder + +default_fees = 100 + +class WalletUtilityGUI(QMainWindow): + def __init__(self): + super().__init__() + self.initUI() + + def initUI(self): + self.setWindowTitle('BAL Wallet Utility') + self.setFixedSize(500, 400) + + # Central widget + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Main layout + layout = QVBoxLayout(central_widget) + + # Wallet input group + wallet_group = QGroupBox("Wallet Settings") + wallet_layout = QVBoxLayout(wallet_group) + + # Wallet path + wallet_path_layout = QHBoxLayout() + wallet_path_layout.addWidget(QLabel("Wallet Path:")) + self.wallet_path_edit = QLineEdit() + self.wallet_path_edit.setPlaceholderText("Select wallet path...") + wallet_path_layout.addWidget(self.wallet_path_edit) + + self.browse_btn = QPushButton("Browse...") + self.browse_btn.clicked.connect(self.browse_wallet) + wallet_path_layout.addWidget(self.browse_btn) + + wallet_layout.addLayout(wallet_path_layout) + + # Password + password_layout = QHBoxLayout() + password_layout.addWidget(QLabel("Password:")) + self.password_edit = QLineEdit() + self.password_edit.setEchoMode(QLineEdit.EchoMode.Password) + self.password_edit.setPlaceholderText("Enter password (if encrypted)") + password_layout.addWidget(self.password_edit) + + wallet_layout.addLayout(password_layout) + + layout.addWidget(wallet_group) + + # Output area + output_group = QGroupBox("Output") + output_layout = QVBoxLayout(output_group) + + self.output_text = QTextEdit() + self.output_text.setReadOnly(True) + output_layout.addWidget(self.output_text) + + layout.addWidget(output_group) + + # Action buttons + buttons_layout = QHBoxLayout() + + self.fix_btn = QPushButton("Fix") + self.fix_btn.clicked.connect(self.fix_wallet) + self.fix_btn.setEnabled(False) + buttons_layout.addWidget(self.fix_btn) + + self.uninstall_btn = QPushButton("Uninstall") + self.uninstall_btn.clicked.connect(self.uninstall_wallet) + self.uninstall_btn.setEnabled(False) + buttons_layout.addWidget(self.uninstall_btn) + + layout.addLayout(buttons_layout) + + # Connections to enable buttons when path is entered + self.wallet_path_edit.textChanged.connect(self.check_inputs) + + def browse_wallet(self): + file_path, _ = QFileDialog.getOpenFileName( + self, + "Select Wallet", + "", + "Electrum Wallet (*.dat)" + ) + if file_path: + self.wallet_path_edit.setText(file_path) + + def check_inputs(self): + wallet_path = self.wallet_path_edit.text().strip() + has_path = bool(wallet_path) and os.path.exists(wallet_path) + + self.fix_btn.setEnabled(has_path) + self.uninstall_btn.setEnabled(has_path) + + def log_message(self, message): + self.output_text.append(message) + + def fix_will_settings_tx_fees(self, json_wallet): + tx_fees = json_wallet.get('will_settings',{}).get('tx_fees',False) + if tx_fees: + json_wallet['will_settings']['baltx_fees'] = json_wallet.get('will_settings',{}).get('tx_fees', default_fees) + del json_wallet['will_settings']['tx_fees'] + return True + return False + + def uninstall_bal(self, json_wallet): + 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 + + def save_wallet(self, json_wallet, storage): + try: + human_readable = not storage.is_encrypted() + storage.write(json.dumps( + json_wallet, + indent=4 if human_readable else None, + sort_keys=bool(human_readable), + cls=MyEncoder, + )) + return True + except Exception as e: + self.log_message(f"Save error: {str(e)}") + return False + + def fix_wallet(self): + self.process_wallet('fix') + + def uninstall_wallet(self): + self.log_message("WARNING: This will remove all BAL settings. This operation cannot be undone.") + self.process_wallet('uninstall') + + def process_wallet(self, command): + wallet_path = self.wallet_path_edit.text().strip() + password = self.password_edit.text() + + if not wallet_path: + self.log_message("ERROR: Please enter wallet path") + return + + if not os.path.exists(wallet_path): + self.log_message("ERROR: Wallet not found") + return + + try: + self.log_message(f"Processing wallet: {wallet_path}") + + storage = WalletStorage(wallet_path) + + # Decrypt if necessary + if storage.is_encrypted(): + if not password: + self.log_message("ERROR: Wallet is encrypted, please enter password") + return + + try: + storage.decrypt(password) + self.log_message("Wallet decrypted successfully") + except Exception as e: + self.log_message(f"ERROR: Wrong password: {str(e)}") + return + + # Read wallet + data = storage.read() + json_wallet = json.loads(data) + + have_to_save = False + message = "" + + if command == 'fix': + have_to_save = self.fix_will_settings_tx_fees(json_wallet) + message = "Fix applied successfully" if have_to_save else "No fix needed" + + elif command == 'uninstall': + have_to_save = self.uninstall_bal(json_wallet) + message = "BAL uninstalled successfully" if have_to_save else "No BAL settings found to uninstall" + + if have_to_save: + if self.save_wallet(json_wallet, storage): + self.log_message(f"SUCCESS: {message}") + else: + self.log_message("ERROR: Failed to save wallet") + else: + self.log_message(f"INFO: {message}") + + except Exception as e: + error_msg = f"ERROR: Processing failed: {str(e)}" + self.log_message(error_msg) + +def main(): + app = QApplication(sys.argv) + + # Check if dependencies are available + try: + from electrum.storage import WalletStorage + from electrum.util import MyEncoder + except ImportError as e: + print(f"ERROR: Cannot import Electrum dependencies: {str(e)}") + return 1 + + window = WalletUtilityGUI() + window.show() + + return app.exec() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/heirs.py b/heirs.py index e9f132c..c35984c 100644 --- a/heirs.py +++ b/heirs.py @@ -14,6 +14,7 @@ from electrum.transaction import PartialTxInput, PartialTxOutput,TxOutpoint,Part import datetime import urllib.request import urllib.parse +import random from .util import Util from .willexecutors import Willexecutors if TYPE_CHECKING: @@ -112,7 +113,9 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet): change = get_change_output(wallet, in_amount, out_amount, fee) if change: outputs.append(change) - + for i in range(0,100): + random.shuffle(outputs) + print(outputs) tx = PartialTransaction.from_io(used_utxos, outputs, locktime=Util.parse_locktime_string(locktime,wallet), version=2) if len(description)>0: tx.description = description[:-1] else: tx.description = "" @@ -385,7 +388,7 @@ class Heirs(dict, Logger): if not utxos: utxos = wallet.get_utxos() willexecutors = Willexecutors.get_willexecutors(bal_plugin) or {} - self.decimal_point=bal_plugin.config.get_decimal_point() + self.decimal_point=bal_plugin.get_decimal_point() no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get() for utxo in utxos: if utxo.value_sats()> 0*tx_fees: diff --git a/manifest.json b/manifest.json index 0c99987..79c4c90 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "BAL", "fullname": "Bitcoin After Life", - "description": "Provides free and decentralized inheritance support
Version: 0.2.2a", + "description": "Provides free and decentralized inheritance support
Version: 0.2.2b", "author":"Svatantrya", "available_for": ["qt"], "icon":"icons/bal32x32.png" diff --git a/qt.py b/qt.py index 4d53abc..9ad0fea 100644 --- a/qt.py +++ b/qt.py @@ -336,12 +336,14 @@ class Plugin(BalPlugin,Logger): def __init__(self, parent, config, name): Logger.__init__(self) self.logger.info("INIT BALPLUGIN") + print("init bal_plugin") BalPlugin.__init__(self, parent, config, name) self.bal_windows={} @hook def init_qt(self,gui_object): + print("hook init qt") self.logger.info("HOOK init qt") try: self.gui_object=gui_object @@ -396,10 +398,16 @@ class Plugin(BalPlugin,Logger): @hook def close_wallet(self,wallet): + print("HOOK close wallet") for winid,win in self.bal_windows.items(): if win.wallet == wallet: win.on_close() - + @hook + def init_keystore(self): + print("init keystore") + @hook + def daemon_wallet_loaded(self,boh,wallet): + print("daemon wallet loaded") def get_window(self,window): w = self.bal_windows.get(window.winId,None) if w is None: @@ -510,6 +518,7 @@ class BalWindow(Logger): self.will_tab = self.create_will_tab() self.ok= False self.disable_plugin = True + self.bal_plugin.get_decimal_point = self.window.get_decimal_point if self.window.wallet: self.wallet = self.window.wallet @@ -628,7 +637,7 @@ class BalWindow(Logger): heir_address.setText(str(heir[0])) heir_amount = PercAmountEdit(self.window.get_decimal_point) if heir: - heir_amount.setText(str(Util.decode_amount(heir[1],self.bal_plugin.config.get_decimal_point()))) + heir_amount.setText(str(Util.decode_amount(heir[1],self.window.get_decimal_point()))) heir_locktime = HeirsLockTimeEdit(self.window,0) if heir: heir_locktime.set_locktime(heir[2]) @@ -673,7 +682,7 @@ class BalWindow(Logger): heir = [ heir_name.text(), heir_address.text(), - Util.encode_amount(heir_amount.text(),self.bal_plugin.config.get_decimal_point()), + Util.encode_amount(heir_amount.text(),self.window.get_decimal_point()), str(heir_locktime.get_locktime()), ] try: @@ -744,7 +753,7 @@ class BalWindow(Logger): f=True if not f: raise 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) + txs = self.heirs.get_transactions(self.bal_plugin,self.window.wallet,self.will_settings['baltx_fees'],None,self.date_to_check) self.logger.info(txs) creation_time = time.time() if txs: @@ -758,7 +767,7 @@ class BalWindow(Logger): tx['description'] = txs[txid].description tx['willexecutor'] = copy.deepcopy(txs[txid].willexecutor) tx['status'] = _("New") - tx['tx_fees'] = txs[txid].tx_fees + tx['baltx_fees'] = txs[txid].tx_fees tx['time'] = creation_time tx['heirs'] = copy.deepcopy(txs[txid].heirs) tx['txchildren'] = [] @@ -770,7 +779,7 @@ class BalWindow(Logger): 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) + return Will.is_will_valid(self.willitems, self.block_to_check, self.date_to_check, self.will_settings['baltx_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): @@ -918,7 +927,7 @@ class BalWindow(Logger): def on_failure(exec_info): self.show_error(f"ERROR:{exec_info}") - fee_per_byte=self.will_settings.get('tx_fees',1) + 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") self.waiting_dialog = BalWaitingDialog(self, msg, task, on_success, on_failure,exe=False) @@ -1107,7 +1116,6 @@ class BalWindow(Logger): 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() @@ -1722,11 +1730,11 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget): self.heir_tx_fees.setMinimum(1) self.heir_tx_fees.setMaximum(10000) - self.heir_tx_fees.setValue(will_settings['tx_fees']) + 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['tx_fees'] = self.heir_tx_fees.value() + 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) @@ -1894,7 +1902,7 @@ class BalBuildWillDialog(BalDialog): self.msg_set_checking('Ok') except WillExpiredException as e: self.msg_set_checking("Expired") - fee_per_byte=self.bal_window.will_settings.get('tx_fees',1) + fee_per_byte=self.bal_window.will_settings.get('baltx_fees',1) return None, Will.invalidate_will(self.bal_window.willitems,self.bal_window.wallet,fee_per_byte) except NoHeirsException: self.msg_set_checking("No Heirs") @@ -2194,7 +2202,7 @@ class HeirList(MyTreeView,MessageBoxMixin): stretch_column=self.Columns.NAME, editable_columns=[self.Columns.NAME,self.Columns.ADDRESS,self.Columns.AMOUNT], ) - self.decimal_point = bal_window.bal_plugin.config.get_decimal_point() + self.decimal_point = bal_window.window.get_decimal_point() self.bal_window = bal_window try: @@ -2340,7 +2348,7 @@ class HeirList(MyTreeView,MessageBoxMixin): 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.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) @@ -2382,7 +2390,7 @@ class HeirList(MyTreeView,MessageBoxMixin): def update_will_settings(self): 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_tx_fees.setValue(int(self.bal_window.will_settings['baltx_fees'])) self.heir_threshold.set_locktime(self.bal_window.will_settings['threshold']) except Exception as e: @@ -2416,7 +2424,7 @@ class PreviewList(MyTreeView): ) self.parent=parent self.bal_window=bal_window - self.decimal_point=bal_window.bal_plugin.config.get_decimal_point + self.decimal_point=bal_window.window.get_decimal_point self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) @@ -2631,7 +2639,11 @@ class PreviewList(MyTreeView): self.update() def check(self): - self.bal_window.check_transactions(self.bal_window.willitems) + will = {} + for wid, w in self.bal_window.willitems: + if w.get_status("VALID"): + will[wid]=w + self.bal_window.check_transactions(will) self.update() def invalidate_will(self): @@ -2733,7 +2745,7 @@ class WillDetailDialog(BalDialog): self.format_fiat_and_units = bal_window.window.format_fiat_and_units self.fx = bal_window.window.fx self.format_fee_rate = bal_window.window.format_fee_rate - self.decimal_point = bal_window.bal_plugin.config.get_decimal_point() + self.decimal_point = bal_window.window.get_decimal_point() self.base_unit_name = decimal_point_to_base_unit_name(self.decimal_point) self.setWindowTitle(_('Will Details')) self.setMinimumSize(670,700) @@ -2905,6 +2917,7 @@ class WillExecutorList(MyTreeView): self.setSortingEnabled(True) self.std_model = self.model() self.config =parent.bal_plugin.config + self.get_decimal_point = parent.bal_plugin.get_decimal_point self.update() @@ -2977,7 +2990,7 @@ class WillExecutorList(MyTreeView): self.parent.willexecutors_list[text]=self.parent.willexecutors_list[edit_key] del self.parent.willexecutors_list[edit_key] if col == self.Columns.BASE_FEE: - self.parent.willexecutors_list[edit_key]["base_fee"] = Util.encode_amount(text,self.config.get_decimal_point()) + self.parent.willexecutors_list[edit_key]["base_fee"] = Util.encode_amount(text,self.get_decimal_point()) if col == self.Columns.ADDRESS: self.parent.willexecutors_list[edit_key]["address"] = text if col == self.Columns.INFO: @@ -3004,7 +3017,7 @@ class WillExecutorList(MyTreeView): labels[self.Columns.SELECTED] = [read_QIcon('confirmed.png'),''] else: labels[self.Columns.SELECTED] = '' - labels[self.Columns.BASE_FEE] = Util.decode_amount(value.get('base_fee',0),self.config.get_decimal_point()) + labels[self.Columns.BASE_FEE] = Util.decode_amount(value.get('base_fee',0),self.get_decimal_point()) if str(value.get('status',0)) == "200": labels[self.Columns.STATUS] = [read_QIcon('status_connected.png'),''] else: diff --git a/will.py b/will.py index 162d21c..b3cd89d 100644 --- a/will.py +++ b/will.py @@ -628,7 +628,7 @@ class WillItem(Logger): self.description = w.get('description',None) self.time = w.get('time',None) self.change = w.get('change',None) - self.tx_fees = w.get('tx_fees',0) + self.tx_fees = w.get('baltx_fees',0) self.father = w.get('Father',None) self.children = w.get('Children',None) self.STATUS = copy.deepcopy(WillItem.STATUS_DEFAULT) @@ -658,7 +658,7 @@ class WillItem(Logger): 'description':self.description, 'time':self.time, 'change':self.change, - 'tx_fees':self.tx_fees + 'baltx_fees':self.tx_fees } for key in self.STATUS: try: