9 Commits

Author SHA1 Message Date
7c1fc04add a lot 2025-10-14 07:50:27 -04:00
fd7e849158 bugfixes 2025-08-31 14:48:10 -04:00
b1b0338bc7 bugfix 2025-08-30 08:39:59 -04:00
a9b50105a6 bugfix 2025-08-29 17:06:47 -04:00
2ec5d060d3 bugfixes 2025-08-29 11:02:07 -04:00
29c63fc5c8 version 2025-08-24 20:38:24 -04:00
461b0cb368 Wizard 2025-08-24 20:28:26 -04:00
d6b37005e8 balconfig 2025-08-11 16:44:55 -04:00
a7b778e0b2 version 0.2.0b 2025-07-23 09:26:27 -04:00
9 changed files with 721 additions and 299 deletions

1
VERSION Normal file
View File

@@ -0,0 +1 @@
0.2.2a

View File

@@ -1,20 +1 @@
from electrum.i18n import _
import subprocess
from . import bal_resources
BUILD_NUMBER = 3
REVISION_NUMBER = 2
VERSION_NUMBER = 0
def _version():
return f'{VERSION_NUMBER}.{REVISION_NUMBER}-{BUILD_NUMBER}'
version = _version()
author = "Bal Enterprise inc."
fullname = _('B.A.L.')
description = ''.join([
"<img src='",bal_resources.icon_path('bal16x16.png'),"'>", _("Bitcoin After Life"), '<br/>',
_("For more information, visit"),
" <a href=\"https://bitcoin-after.life/\">https://bitcoin-after.life/</a><br/>",
"<p style='font-size:8pt;vertialAlign:bottom'>Version: ", _version(),"</p>"
])
#available_for = ['qt', 'cmdline', 'qml']
available_for = ['qt']

154
bal.py
View File

@@ -1,5 +1,6 @@
import random import random
import os import os
import zipfile as zipfile_lib
from electrum.plugin import BasePlugin from electrum.plugin import BasePlugin
from electrum import json_db from electrum import json_db
@@ -9,112 +10,119 @@ import os
json_db.register_dict('heirs', tuple, None) json_db.register_dict('heirs', tuple, None)
json_db.register_dict('will', lambda x: get_will(x), None) json_db.register_dict('will', lambda x: get_will(x), None)
json_db.register_dict('will_settings', lambda x:x, None) json_db.register_dict('will_settings', lambda x:x, None)
from electrum.logging import get_logger from electrum.logging import get_logger
def get_will(x): def get_will(x):
try: try:
x['tx']=tx_from_any(x['tx']) x['tx']=tx_from_any(x['tx'])
except Exception as e: except Exception as e:
raise e raise e
return x 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 = self.default
return v
def set(self,value,save=True):
self.config.set_key(self.name,value,save=save)
class BalPlugin(BasePlugin): class BalPlugin(BasePlugin):
LOCKTIME_TIME = "bal_locktime_time" LATEST_VERSION = '1'
LOCKTIME_BLOCKS = "bal_locktime_blocks" KNOWN_VERSIONS = ('0', '1')
LOCKTIMEDELTA_TIME = "bal_locktimedelta_time" assert LATEST_VERSION in KNOWN_VERSIONS
LOCKTIMEDELTA_BLOCKS = "bal_locktimedelta_blocks" def version():
TX_FEES = "bal_tx_fees" try:
BROADCAST = "bal_broadcast" f=""
ASK_BROADCAST = "bal_ask_broadcast" with open("VERSION","r") as f:
INVALIDATE = "bal_invalidate" f = str(f.readline())
ASK_INVALIDATE = "bal_ask_invalidate" return f
PREVIEW = "bal_preview" except:
SAVE_TXS = "bal_save_txs" return "unknown"
WILLEXECUTORS = "bal_willexecutors" SIZE = (159, 97)
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"
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]
zipfile="/".join(self.plugin_dir.split("/")[:-1])
#print("real path",os.path.realpath(__file__))
#self.logger.info(self.base_dir)
#print("base_dir:", self.base_dir)
#print("suca:",zipfile)
#print("plugin_dir:", self.plugin_dir)
import sys
sys.path.insert(0, zipfile)
#print("sono state listate?")
self.parent = parent
self.config = config
self.name = name
self.ASK_BROADCAST = BalConfig(config, "bal_ask_broadcast", True)
DEFAULT_SETTINGS={ self.BROADCAST = BalConfig(config, "bal_broadcast", True)
LOCKTIME_TIME: 90, self.LOCKTIME_TIME = BalConfig(config, "bal_locktime_time", 90)
LOCKTIME_BLOCKS: 144*90, self.LOCKTIME_BLOCKS = BalConfig(config, "bal_locktime_blocks", 144*90)
LOCKTIMEDELTA_TIME: 7, self.LOCKTIMEDELTA_TIME = BalConfig(config, "bal_locktimedelta_time", 7)
LOCKTIMEDELTA_BLOCKS:144*7, self.LOCKTIMEDELTA_BLOCKS = BalConfig(config, "bal_locktimedelta_blocks", 144*7)
TX_FEES: 100, self.ENABLE_MULTIVERSE = BalConfig(config, "bal_enable_multiverse", False)
BROADCAST: True, self.TX_FEES = BalConfig(config, "bal_tx_fees", 100)
ASK_BROADCAST: True, self.INVALIDATE = BalConfig(config, "bal_invalidate", True)
INVALIDATE: True, self.ASK_INVALIDATE = BalConfig(config, "bal_ask_invalidate", True)
ASK_INVALIDATE: True, self.PREVIEW = BalConfig(config, "bal_preview", True)
PREVIEW: True, self.SAVE_TXS = BalConfig(config, "bal_save_txs", True)
SAVE_TXS: True, self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True)
PING_WILLEXECUTORS: False, self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
ASK_PING_WILLEXECUTORS: False, self.ASK_PING_WILLEXECUTORS = BalConfig(config, "bal_ask_ping_willexecutors", True)
NO_WILLEXECUTOR: False, self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
HIDE_REPLACED:True, self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
HIDE_INVALIDATED:True, self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
ALLOW_REPUSH: False, self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
WILLEXECUTORS: { self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
'https://bitcoin-after.life:9137': { self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", {
"mainnet": {
'https://we.bitcoin-after.life': {
"base_fee": 100000, "base_fee": 100000,
"status": "New", "status": "New",
"info":"Bitcoin After Life Will Executor", "info":"Bitcoin After Life Will Executor",
"address":"bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", "address":"bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected":True "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): def resource_path(self,*parts):
return os.path.join(self.plugin_dir, *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): def hide_invalidated(self):
self._hide_invalidated = not self._hide_invalidated 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): def hide_replaced(self):
self._hide_replaced = not self._hide_replaced 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): def validate_will_settings(self,will_settings):
if int(will_settings.get('tx_fees',1))<1: if int(will_settings.get('tx_fees',1))<1:
will_settings['tx_fees']=1 will_settings['tx_fees']=1

View File

@@ -2,7 +2,7 @@ import os
PLUGIN_DIR = os.path.split(os.path.realpath(__file__))[0] PLUGIN_DIR = os.path.split(os.path.realpath(__file__))[0]
DEFAULT_ICON = 'bal32x32.png' DEFAULT_ICON = 'bal32x32.png'
DEFAULT_ICON_PATH = 'icons' DEFAULT_ICON_PATH = ''
def icon_path(icon_basename: str = DEFAULT_ICON): def icon_path(icon_basename: str = DEFAULT_ICON):

View File

@@ -14,7 +14,6 @@ from electrum.transaction import PartialTxInput, PartialTxOutput,TxOutpoint,Part
import datetime import datetime
import urllib.request import urllib.request
import urllib.parse import urllib.parse
from .bal import BalPlugin
from .util import Util from .util import Util
from .willexecutors import Willexecutors from .willexecutors import Willexecutors
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -387,7 +386,7 @@ class Heirs(dict, Logger):
utxos = wallet.get_utxos() utxos = wallet.get_utxos()
willexecutors = Willexecutors.get_willexecutors(bal_plugin) or {} willexecutors = Willexecutors.get_willexecutors(bal_plugin) or {}
self.decimal_point=bal_plugin.config.get_decimal_point() 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: for utxo in utxos:
if utxo.value_sats()> 0*tx_fees: if utxo.value_sats()> 0*tx_fees:
balance += utxo.value_sats() balance += utxo.value_sats()
@@ -564,10 +563,7 @@ class Heirs(dict, Logger):
def validate_amount(amount): def validate_amount(amount):
try: try:
if Util.is_perc(amount): famount = float(amount[:-1]) if Util.is_perc(amount) else float(amount)
famount = float(amount[:-1])
else:
famount = float(amount)
if famount <= 0.00000001: if famount <= 0.00000001:
raise AmountNotValid(f"amount have to be positive {famount} < 0") raise AmountNotValid(f"amount have to be positive {famount} < 0")
except Exception as e: except Exception as e:
@@ -576,9 +572,8 @@ class Heirs(dict, Logger):
def validate_locktime(locktime,timestamp_to_check=False): def validate_locktime(locktime,timestamp_to_check=False):
try: try:
locktime = Util.parse_locktime_string(locktime,None)
if timestamp_to_check: if timestamp_to_check:
if locktime < timestamp_to_check: if Util.parse_locktime_string(locktime,None) < timestamp_to_check:
raise HeirExpiredException() raise HeirExpiredException()
except Exception as e: except Exception as e:
raise LocktimeNotValid(f"locktime string not properly formatted, {e}") raise LocktimeNotValid(f"locktime string not properly formatted, {e}")

View File

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

698
qt.py

File diff suppressed because it is too large Load Diff

22
will.py
View File

@@ -15,8 +15,8 @@ MIN_LOCKTIME = 1
MIN_BLOCK = 1 MIN_BLOCK = 1
_logger = get_logger(__name__) _logger = get_logger(__name__)
#return an array with the list of children
class Will: class Will:
#return an array with the list of children
def get_children(will,willid): def get_children(will,willid):
out = [] out = []
for _id in will: for _id in will:
@@ -72,6 +72,8 @@ class Will:
for txin in will[wid].tx.inputs(): for txin in will[wid].tx.inputs():
txid = txin.prevout.txid.hex() txid = txin.prevout.txid.hex()
if txid in will: if txid in will:
#print(will[txid].tx.outputs())
#print(txin.prevout.out_idx)
change = will[txid].tx.outputs()[txin.prevout.out_idx] change = will[txid].tx.outputs()[txin.prevout.out_idx]
txin._trusted_value_sats = change.value txin._trusted_value_sats = change.value
try: try:
@@ -88,8 +90,11 @@ class Will:
to_delete = [] to_delete = []
to_add = {} to_add = {}
#add info from wallet #add info from wallet
willitems={}
for wid in will: for wid in will:
Will.add_info_from_will(will,wid,wallet) Will.add_info_from_will(will,wid,wallet)
willitems[wid]=WillItem(will[wid])
will=willitems
errors ={} errors ={}
for wid in will: for wid in will:
@@ -110,7 +115,7 @@ class Will:
outputs = will[wid].tx.outputs() outputs = will[wid].tx.outputs()
ow=will[wid] ow=will[wid]
ow.normalize_locktime(others_inputs) ow.normalize_locktime(others_inputs)
will[wid]=ow.to_dict() will[wid]=WillItem(ow.to_dict())
for i in range(0,len(outputs)): for i in range(0,len(outputs)):
Will.change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add) Will.change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add)
@@ -329,7 +334,7 @@ class Will:
return tx return tx
else: else:
_logger.debug("balance - fee <=0") _logger.debug(f"balance({balance}) - fee({fee}) <=0")
pass pass
else: else:
_logger.debug("len utxo_to_spend <=0") _logger.debug("len utxo_to_spend <=0")
@@ -743,10 +748,11 @@ class WillItem(Logger):
else: else:
return "#ffffff" return "#ffffff"
class WillException(Exception):
class WillExpiredException(Exception):
pass pass
class NotCompleteWillException(Exception): class WillExpiredException(WillException):
pass
class NotCompleteWillException(WillException):
pass pass
class HeirChangeException(NotCompleteWillException): class HeirChangeException(NotCompleteWillException):
pass pass
@@ -760,9 +766,9 @@ class NoWillExecutorNotPresent(NotCompleteWillException):
pass pass
class WillExecutorNotPresent(NotCompleteWillException): class WillExecutorNotPresent(NotCompleteWillException):
pass pass
class NoHeirsException(Exception): class NoHeirsException(WillException):
pass pass
class AmountException(Exception): class AmountException(WillException):
pass pass
class PercAmountException(AmountException): class PercAmountException(AmountException):
pass pass

View File

@@ -8,39 +8,54 @@ from electrum import constants
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.gui.qt.util import WaitingDialog from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _ from electrum.i18n import _
from .bal import BalPlugin
from .util import Util from .util import Util
DEFAULT_TIMEOUT = 5 DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__) _logger = get_logger(__name__)
class Willexecutors: class Willexecutors:
def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
willexecutors = bal_plugin.config_get(bal_plugin.WILLEXECUTORS)
for w in willexecutors:
Willexecutors.initialize_willexecutor(willexecutors[w],w)
bal=bal_plugin.DEFAULT_SETTINGS[bal_plugin.WILLEXECUTORS] def save(bal_plugin, willexecutors):
aw=bal_plugin.WILLEXECUTORS.get()
aw[constants.net.NET_NAME]=willexecutors
bal_plugin.WILLEXECUTORS.set(aw)
def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors=willexecutors.get(constants.net.NET_NAME,{})
to_del=[]
for w in willexecutors:
if not isinstance(willexecutors[w],dict):
to_del.append(w)
continue
Willexecutors.initialize_willexecutor(willexecutors[w],w)
for w in to_del:
print("ERROR: WILLEXECUTOR TO DELETE:", w)
del willexecutors[w]
bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME,{})
for bal_url,bal_executor in bal.items(): for bal_url,bal_executor in bal.items():
if not bal_url in willexecutors: if not bal_url in willexecutors:
_logger.debug("replace bal") _logger.debug(f"force add {bal_url} willexecutor")
willexecutors[bal_url]=bal_executor willexecutors[bal_url] = bal_executor
if update: if update:
found = False found = False
for url,we in willexecutors.items(): for url,we in willexecutors.items():
if Willexecutors.is_selected(we): if Willexecutors.is_selected(we):
found = True found = True
if found or force: 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 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?")) ping_willexecutors = bal_window.window.question(_("Contact willexecutors servers to update payment informations?"))
if ping_willexecutors: if ping_willexecutors:
if task: if task:
bal_window.ping_willexecutors(willexecutors) bal_window.ping_willexecutors(willexecutors,task)
else: else:
bal_window.ping_willexecutors_task(willexecutors) 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): def is_selected(willexecutor,value=None):
if not willexecutor: if not willexecutor:
return False return False
@@ -90,7 +105,7 @@ class Willexecutors:
raise ErrorConnectingServer('You are offline.') raise ErrorConnectingServer('You are offline.')
_logger.debug(f'<-- {method} {url} {data}') _logger.debug(f'<-- {method} {url} {data}')
headers = {} headers = {}
headers['user-agent'] = 'BalPlugin' headers['user-agent'] = f"BalPlugin v:{BalPlugin.version()}"
headers['Content-Type']='text/plain' headers['Content-Type']='text/plain'
try: try:
@@ -163,6 +178,7 @@ class Willexecutors:
if constants.net.NET_NAME!="mainnet": if constants.net.NET_NAME!="mainnet":
netname=constants.net.NET_NAME netname=constants.net.NET_NAME
w = Willexecutors.send_request('get',url+"/"+netname+"/info") w = Willexecutors.send_request('get',url+"/"+netname+"/info")
willexecutor['url']=url willexecutor['url']=url
willexecutor['status'] = w['status'] willexecutor['status'] = w['status']
willexecutor['base_fee'] = w['base_fee'] willexecutor['base_fee'] = w['base_fee']
@@ -182,6 +198,7 @@ class Willexecutors:
if not status is None: if not status is None:
willexecutor['status'] = status willexecutor['status'] = status
willexecutor['selected'] = Willexecutors.is_selected(willexecutor,selected) willexecutor['selected'] = Willexecutors.is_selected(willexecutor,selected)
def download_list(bal_plugin): def download_list(bal_plugin):
try: try:
l = Willexecutors.send_request('get',"https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100") l = Willexecutors.send_request('get',"https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100")
@@ -189,10 +206,12 @@ class Willexecutors:
for w in l: for w in l:
willexecutor=l[w] willexecutor=l[w]
Willexecutors.initialize_willexecutor(willexecutor,w,'New',False) 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 return l
except Exception as e: except Exception as e:
_logger.error(f"error downloading willexecutors list:{e}") _logger.error(f"Failed to download willexecutors list: {e}")
return {} return {}
def get_willexecutors_list_from_json(bal_plugin): def get_willexecutors_list_from_json(bal_plugin):
try: try:
@@ -201,7 +220,7 @@ class Willexecutors:
for w in willexecutors: for w in willexecutors:
willexecutor=willexecutors[w] willexecutor=willexecutors[w]
Willexecutors.initialize_willexecutor(willexecutor,w,'New',False) 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 return h
except Exception as e: except Exception as e:
_logger.error(f"error opening willexecutors json: {e}") _logger.error(f"error opening willexecutors json: {e}")
@@ -216,3 +235,22 @@ class Willexecutors:
except Exception as e: except Exception as e:
_logger.error(f"error contacting {url} for checking txs {e}") _logger.error(f"error contacting {url} for checking txs {e}")
raise 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']
)