From 4c84bc2bbebcd3803df5b2d8ca7b94cd485eeb53 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Tue, 26 Aug 2025 15:26:19 +1000 Subject: [PATCH 1/9] fix: Use hsd.hns.au to get name from namehash in order to only import account once --- account.py | 13 ++++++++----- main.py | 6 ++---- render.py | 8 +++++--- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/account.py b/account.py index 3079b4a..0392fd6 100644 --- a/account.py +++ b/account.py @@ -13,14 +13,11 @@ dotenv.load_dotenv() HSD_API = os.getenv("HSD_API","") HSD_IP = os.getenv("HSD_IP","localhost") -HSD_NETWORK = os.getenv("HSD_NETWORK") +HSD_NETWORK = os.getenv("HSD_NETWORK", "main") HSD_WALLET_PORT = 12039 HSD_NODE_PORT = 12037 -if not HSD_NETWORK: - HSD_NETWORK = "main" -else: - HSD_NETWORK = HSD_NETWORK.lower() +HSD_NETWORK = HSD_NETWORK.lower() if HSD_NETWORK == "simnet": HSD_WALLET_PORT = 15039 @@ -32,6 +29,12 @@ elif HSD_NETWORK == "regtest": HSD_WALLET_PORT = 14039 HSD_NODE_PORT = 14037 +INTERNAL_NODE = os.getenv("INTERNAL_HSD","false").lower() in ["1","true","yes"] +if INTERNAL_NODE: + if HSD_API == "": + # Use a random API KEY + HSD_API = "firewallet-" + str(int(time.time())) + SHOW_EXPIRED = os.getenv("SHOW_EXPIRED") if SHOW_EXPIRED is None: diff --git a/main.py b/main.py index 5741698..4b2300b 100644 --- a/main.py +++ b/main.py @@ -56,10 +56,6 @@ def blocks_to_time(blocks: int) -> str: if hours == 0: return f"{days} days" return f"{days} days {hours} hrs" - - - - @app.route('/') def index(): @@ -82,6 +78,8 @@ def index(): return render_template("index.html", account=account, plugins=plugins) info = gitinfo.get_git_info() + if info is None: + return render_template("index.html", account=account, plugins=plugins) branch = info['refs'] commit = info['commit'] if commit != latestVersion(branch): diff --git a/render.py b/render.py index 847b3be..e48ec3f 100644 --- a/render.py +++ b/render.py @@ -6,7 +6,7 @@ from domainLookup import punycode_to_emoji import os from handywrapper import api import threading -import account +import requests # Get Explorer URL TX_EXPLORER_URL = os.getenv("EXPLORER_TX") @@ -535,8 +535,10 @@ def renderDomainAsync(namehash: str) -> None: if namehash in cache: return - # Fetch the name outside the lock (network call) - name = account.hsd.rpc_getNameByHash(namehash) + # Fetch the name outside the lock (network call) using hsd.hns.au + # name = account.hsd.rpc_getNameByHash(namehash) + name = requests.get(f"https://hsd.hns.au/api/v1/namehash/{namehash}").json() + if name["error"] is None: name = name["result"] rendered = renderDomain(name) From 5ff8960b7b310dbe4f3f037338a5738ea031f515 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Tue, 26 Aug 2025 16:44:10 +1000 Subject: [PATCH 2/9] feat: Add initial internal node option --- .gitignore | 2 + FireWalletBrowser.bsdesign | Bin 343534 -> 344794 bytes README.md | 2 + account.py | 213 ++++++++++++++++++++++++++++++++++++- hsdconfig.json | 8 ++ main.py | 24 +++-- server.py | 2 +- templates/settings.html | 42 ++++---- templates/welcome.html | 47 ++++++++ 9 files changed, 310 insertions(+), 30 deletions(-) create mode 100644 hsdconfig.json create mode 100644 templates/welcome.html diff --git a/.gitignore b/.gitignore index f73a8c4..d8ad3fb 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ cache/ build/ dist/ hsd/ +hsd-data/ +hsd.lock diff --git a/FireWalletBrowser.bsdesign b/FireWalletBrowser.bsdesign index 3e627ef733c7c56a9d750c3f7fe134af96428d63..6eb424148cb738aaec6d8ff3e85ba6b4f3821490 100644 GIT binary patch delta 29301 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 YSVgkUBkuCn)_3S{liC7W*jOO{1!dRcx&QzG delta 27209 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 a&*&^}ukX?j5W2j3ff dict[str, bool]: + prerequisites = { + "node": False, + "npm": False, + "git": False, + "hsd": False + } + + # Check if node is installed and get version + nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True) + if nodeSubprocess.returncode == 0: + major_version = int(nodeSubprocess.stdout.strip().lstrip('v').split('.')[0]) + if major_version >= HSD_CONFIG.get("minNodeVersion", 20): + prerequisites["node"] = True + + # Check if npm is installed + npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True) + if npmSubprocess.returncode == 0: + major_version = int(npmSubprocess.stdout.strip().split('.')[0]) + if major_version >= HSD_CONFIG.get("minNPMVersion", 8): + prerequisites["npm"] = True + + # Check if git is installed + gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True) + if gitSubprocess.returncode == 0: + prerequisites["git"] = True + + # Check if hsd is installed + if os.path.exists("./hsd/bin/hsd"): + prerequisites["hsd"] = True + + + + + return prerequisites + + + +def hsdInit(): + if not HSD_INTERNAL_NODE: + return + prerequisites = checkPreRequisites() + + PREREQ_MESSAGES = { + "node": "Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})", + "npm": "Install npm (version >= {minNPMVersion}) - usually comes with Node.js", + "git": "Install Git from https://git-scm.com/downloads"} + + + # Check if all prerequisites are met (except hsd) + if not all(prerequisites[key] for key in prerequisites if key != "hsd"): + print("HSD Internal Node prerequisites not met:") + for key, value in prerequisites.items(): + if not value: + print(f" - {key} is missing or does not meet the version requirement.") + exit(1) + return + + # Check if hsd is installed + if not prerequisites["hsd"]: + print("HSD not found, installing...") + # If hsd folder exists, remove it + if os.path.exists("hsd"): + os.rmdir("hsd") + + # Clone hsd repo + gitClone = subprocess.run(["git", "clone", "--depth", "1", "--branch", HSD_CONFIG.get("version", "latest"), "https://github.com/handshake-org/hsd.git", "hsd"], capture_output=True, text=True) + if gitClone.returncode != 0: + print("Failed to clone hsd repository:") + print(gitClone.stderr) + exit(1) + print("Cloned hsd repository.") + # Install hsd dependencies + print("Installing hsd dependencies...") + npmInstall = subprocess.run(["npm", "install"], cwd="hsd", capture_output=True, text=True) + if npmInstall.returncode != 0: + print("Failed to install hsd dependencies:") + print(npmInstall.stderr) + exit(1) + print("Installed hsd dependencies.") + +def hsdStart(): + global HSD_PROCESS + if not HSD_INTERNAL_NODE: + return + + # Check if hsd was started in the last 30 seconds + if os.path.exists("hsd.lock"): + lock_time = os.path.getmtime("hsd.lock") + if time.time() - lock_time < 30: + print("HSD was started recently, skipping start.") + return + else: + os.remove("hsd.lock") + + print("Starting HSD...") + # Create a lock file + with open("hsd.lock", "w") as f: + f.write(str(time.time())) + + # Config lookups with defaults + chain_migrate = HSD_CONFIG.get("chainMigrate", False) + wallet_migrate = HSD_CONFIG.get("walletMigrate", False) + spv = HSD_CONFIG.get("spv", False) + + # Base command + cmd = [ + "node", + "./hsd/bin/hsd", + f"--network={HSD_NETWORK}", + f"--prefix={os.path.join(os.getcwd(), 'hsd-data')}", + f"--api-key={HSD_API}", + "--agent=FireWallet", + "--http-host=127.0.0.1", + "--log-console=false" + ] + + # Conditionally add migration flags + if chain_migrate: + cmd.append(f"--chain-migrate={chain_migrate}") + if wallet_migrate: + cmd.append(f"--wallet-migrate={wallet_migrate}") + if spv: + cmd.append("--spv") + + + # Launch process + HSD_PROCESS = subprocess.Popen( + cmd, + cwd=os.getcwd(), + text=True + ) + + print(f"HSD started with PID {HSD_PROCESS.pid}") + + atexit.register(hsdStop) + + # Handle Ctrl+C + try: + signal.signal(signal.SIGINT, lambda s, f: (hsdStop(), sys.exit(0))) + signal.signal(signal.SIGTERM, lambda s, f: (hsdStop(), sys.exit(0))) + except: + pass + + +def hsdStop(): + global HSD_PROCESS + + if HSD_PROCESS is None: + return + + print("Stopping HSD...") + + # Send SIGINT (like Ctrl+C) + HSD_PROCESS.send_signal(signal.SIGINT) + + try: + HSD_PROCESS.wait(timeout=10) # wait for graceful exit + print("HSD shut down cleanly.") + except subprocess.TimeoutExpired: + print("HSD did not exit yet, is it alright???") + + # Clean up lock file + if os.path.exists("hsd.lock"): + os.remove("hsd.lock") + + HSD_PROCESS = None + +def hsdRestart(): + hsdStop() + time.sleep(2) + hsdStart() + + +checkPreRequisites() +hsdInit() +hsdStart() +# endregion \ No newline at end of file diff --git a/hsdconfig.json b/hsdconfig.json new file mode 100644 index 0000000..9484276 --- /dev/null +++ b/hsdconfig.json @@ -0,0 +1,8 @@ +{ +"version": "v8.0.0", +"chainMigrate":4, +"walletMigrate":7, +"minNodeVersion":20, +"minNpmVersion":8, +"spv": true +} \ No newline at end of file diff --git a/main.py b/main.py index 4b2300b..911a1ba 100644 --- a/main.py +++ b/main.py @@ -1150,14 +1150,13 @@ def settings(): if not os.path.exists(".git"): return render_template("settings.html", account=account, - hsd_version=account_module.hsdVersion(False), - error=error,success=success,version="Error") + error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE) info = gitinfo.get_git_info() if not info: return render_template("settings.html", account=account, hsd_version=account_module.hsdVersion(False), - error=error,success=success,version="Error") + error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE) branch = info['refs'] if branch != "main": @@ -1172,7 +1171,7 @@ def settings(): version += ' (New version available)' return render_template("settings.html", account=account, hsd_version=account_module.hsdVersion(False), - error=error,success=success,version=version) + error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE) @app.route('/settings/') def settings_action(action): @@ -1189,19 +1188,21 @@ def settings_action(action): if 'error' in resp: return redirect("/settings?error=" + str(resp['error'])) return redirect("/settings?success=Rescan started") - elif action == "resend": + + if action == "resend": resp = account_module.resendTXs() if 'error' in resp: return redirect("/settings?error=" + str(resp['error'])) return redirect("/settings?success=Resent transactions") - elif action == "zap": + if action == "zap": resp = account_module.zapTXs(request.cookies.get("account")) if type(resp) == dict and 'error' in resp: return redirect("/settings?error=" + str(resp['error'])) return redirect("/settings?success=Zapped transactions") - elif action == "xpub": + + if action == "xpub": xpub = account_module.getxPub(request.cookies.get("account")) content = "

" content += f"" @@ -1212,6 +1213,12 @@ def settings_action(action): title="xPub Key", content=f"{xpub}{content}") + if action == "restart": + resp = account_module.hsdRestart() + return render_template("message.html", account=account, + title="Restarting", + content="The node is restarting. This may take a minute or two. You can close this window.") + return redirect("/settings?error=Invalid action") @app.route('/settings/upload', methods=['POST']) @@ -1259,6 +1266,9 @@ def login(): wallets = account_module.listWallets() wallets = render.wallets(wallets) + # If there are no wallets redirect to either register or import + if len(wallets) == 0: + return redirect("/welcome") if 'message' in request.args: return render_template("login.html", diff --git a/server.py b/server.py index 396a7a7..f8e1153 100644 --- a/server.py +++ b/server.py @@ -32,7 +32,7 @@ def gunicornServer(): gunicorn_app.run() -if __name__ == '__main__': +if __name__ == '__main__': # Check if --gunicorn is in the command line arguments if "--gunicorn" in sys.argv: gunicornServer() diff --git a/templates/settings.html b/templates/settings.html index c3499bd..e7cba1d 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -69,24 +69,30 @@

Node Settings

HSD Version: v{{hsd_version}} -
Settings that affect all wallets
-
    -
  • -
    Rescan -

    Rescan

    Rescan the blockchain for transactions -
    -
  • -
  • -
    Resend -

    Resend unconfirmed transactions

    Resend any transactions that haven't been mined yet. -
    -
  • -
  • -
    Zap -

    Delete unconfirmed transactions

    This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks) -
    -
  • -
+
Settings that affect all wallets
    +
  • +
    Rescan +

    Rescan

    Rescan the blockchain for transactions +
    +
  • +
  • +
    Resend +

    Resend unconfirmed transactions

    Resend any transactions that haven't been mined yet. +
    +
  • +
  • +
    Zap +

    Delete unconfirmed transactions

    This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks) +
    +
  • + {% if internal %} +
  • +
    Restart Node +

    Restart Internal Node

    This will attempt to restart the HSD node +
    +
  • + {% endif %} +
diff --git a/templates/welcome.html b/templates/welcome.html new file mode 100644 index 0000000..44b8944 --- /dev/null +++ b/templates/welcome.html @@ -0,0 +1,47 @@ + + + + + + + Welcome to FireWallet + + + + + + + + + + + +
+
+
+

{{error}}

+
+
+
+
+ +
+
+
+
+

Welcome to FireWallet!

+
+ +
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file From 7fdc4a312261c1d749e53489a26c37769fb1c4a0 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Tue, 26 Aug 2025 17:31:25 +1000 Subject: [PATCH 3/9] fix: SPV causes some domains to not be recognized as owned by the wallet --- account.py | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/account.py b/account.py index e74b423..c9f111e 100644 --- a/account.py +++ b/account.py @@ -489,7 +489,12 @@ def send(account, address, amount): def isOwnDomain(account, name: str): # Get domain domain_info = getDomain(name) - owner = getAddressFromCoin(domain_info['info']['owner']['hash'],domain_info['info']['owner']['index']) + if 'info' not in domain_info or domain_info['info'] is None: + return False + if 'owner' not in domain_info['info']: + return False + + owner = getAddressFromCoin(domain_info['info']['owner'].get("hash"),domain_info['info']['owner'].get("index")) # Select the account hsw.rpc_selectWallet(account) account = hsw.rpc_getAccount(owner) @@ -529,14 +534,40 @@ def getDomain(domain: str): "message": response['error']['message'] } } + + # If info is None grab from hsd.hns.au + if response['result'] is None or response['result'].get('info') is None: + response = requests.get(f"https://hsd.hns.au/api/v1/name/{domain}").json() + if 'error' in response: + return { + "error": { + "message": response['error'] + } + } + return response + return response['result'] +def isKnownDomain(domain: str) -> bool: + # Get the domain + response = hsd.rpc_getNameInfo(domain) + if response['error'] is not None: + return False + + # If info is None grab from hsd.hns.au + if response['result'] is None or response['result'].get('info') is None: + return False + return True + def getAddressFromCoin(coinhash: str, coinindex = 0): # Get the address from the hash response = requests.get(get_node_api_url(f"coin/{coinhash}/{coinindex}")) if response.status_code != 200: - print(f"Error getting address from coin: {response.text}") - return "No Owner" + # Try to get coin from hsd.hns.au + response = requests.get(f"https://hsd.hns.au/api/v1/coin/{coinhash}/{coinindex}") + if response.status_code != 200: + print(f"Error getting address from coin") + return "No Owner" data = response.json() if 'address' not in data: print(json.dumps(data, indent=4)) @@ -748,7 +779,9 @@ def getPendingRegisters(account): for bid in bids: if bid['name'] == domain['name']: if bid['value'] == domain['highest']: - pending.append(bid) + # Double check the domain is actually in the node + if isKnownDomain(domain['name']): + pending.append(bid) return pending @@ -1483,7 +1516,6 @@ def get_wallet_api_url(path=''): return base_url - # region HSD Internal Node @@ -1592,13 +1624,15 @@ def hsdStart(): chain_migrate = HSD_CONFIG.get("chainMigrate", False) wallet_migrate = HSD_CONFIG.get("walletMigrate", False) spv = HSD_CONFIG.get("spv", False) + prefix = HSD_CONFIG.get("prefix", os.path.join(os.getcwd(), "hsd-data")) + # Base command cmd = [ "node", "./hsd/bin/hsd", f"--network={HSD_NETWORK}", - f"--prefix={os.path.join(os.getcwd(), 'hsd-data')}", + f"--prefix={prefix}", f"--api-key={HSD_API}", "--agent=FireWallet", "--http-host=127.0.0.1", From 26c5b4a4fa6b7b0e9082df30ce51fe4e08712caa Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Tue, 26 Aug 2025 18:04:07 +1000 Subject: [PATCH 4/9] feat: Update configuration storage and overrides --- .gitignore | 1 + README.md | 14 ++++++++++++++ account.py | 30 ++++++++++++++++++++---------- hsdconfig.json | 8 -------- 4 files changed, 35 insertions(+), 18 deletions(-) delete mode 100644 hsdconfig.json diff --git a/.gitignore b/.gitignore index d8ad3fb..b3539aa 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ dist/ hsd/ hsd-data/ hsd.lock +hsdconfig.json diff --git a/README.md b/README.md index 46745c4..9a3d2fe 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,20 @@ INTERNAL_HSD: Use internal HSD node (true/false) ``` + +# Internal HSD + +If you set INTERNAL_HSD=true in the .env file the wallet will start and manage its own HSD node. If you want to override the default HSD config create a file called hsdconfig.json in the same directory as main.py and change the values you want to override. For example to disable SPV and use an existing bob wallet sync (on linux) and set the agent to "SuperCoolDev" you could use the following: +```json +{ + "spv": false, + "prefix":"~/.config/Bob/hsd_data", + "flags":[ + "--agent=SuperCoolDev" + ] +} +``` + ## Warnings - This is a work in progress and is not guaranteed to work diff --git a/account.py b/account.py index c9f111e..d427f8c 100644 --- a/account.py +++ b/account.py @@ -48,18 +48,25 @@ if SHOW_EXPIRED is None: HSD_PROCESS = None # Get hsdconfig.json -HSD_CONFIG = {} +HSD_CONFIG = { + "version": "v8.0.0", + "chainMigrate": 4, + "walletMigrate": 7, + "minNodeVersion": 20, + "minNpmVersion": 8, + "spv": False, + "flags": [ + "--agent=FireWallet" + ] +} if not os.path.exists('hsdconfig.json'): - # Pull from the latest git - response = requests.get("https://git.woodburn.au/nathanwoodburn/firewalletbrowser/raw/branch/main/hsdconfig.json") - if response.status_code == 200: - with open('hsdconfig.json', 'w') as f: - f.write(response.text) - HSD_CONFIG = response.json() + with open('hsdconfig.json', 'w') as f: + f.write(json.dumps(HSD_CONFIG, indent=4)) else: with open('hsdconfig.json') as f: - HSD_CONFIG = json.load(f) - + hsdConfigTMP = json.load(f) + for key in hsdConfigTMP: + HSD_CONFIG[key] = hsdConfigTMP[key] hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT) hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT) @@ -1634,7 +1641,6 @@ def hsdStart(): f"--network={HSD_NETWORK}", f"--prefix={prefix}", f"--api-key={HSD_API}", - "--agent=FireWallet", "--http-host=127.0.0.1", "--log-console=false" ] @@ -1647,6 +1653,10 @@ def hsdStart(): if spv: cmd.append("--spv") + # Add flags + if len(HSD_CONFIG.get("flags",[])) > 0: + for flag in HSD_CONFIG.get("flags",[]): + cmd.append(flag) # Launch process HSD_PROCESS = subprocess.Popen( diff --git a/hsdconfig.json b/hsdconfig.json deleted file mode 100644 index 9484276..0000000 --- a/hsdconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ -"version": "v8.0.0", -"chainMigrate":4, -"walletMigrate":7, -"minNodeVersion":20, -"minNpmVersion":8, -"spv": true -} \ No newline at end of file From f2cda461ba433a3d43225427ec4f1e46cbc45922 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Thu, 28 Aug 2025 16:42:12 +1000 Subject: [PATCH 5/9] feat: Add SPV features to fix accoutn balances --- account.py | 249 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 229 insertions(+), 20 deletions(-) diff --git a/account.py b/account.py index d427f8c..8c6df1d 100644 --- a/account.py +++ b/account.py @@ -11,6 +11,9 @@ import subprocess import atexit import signal import sys +import threading +import sqlite3 +from functools import wraps dotenv.load_dotenv() @@ -46,6 +49,7 @@ if SHOW_EXPIRED is None: SHOW_EXPIRED = False HSD_PROCESS = None +SPV_MODE = None # Get hsdconfig.json HSD_CONFIG = { @@ -59,6 +63,9 @@ HSD_CONFIG = { "--agent=FireWallet" ] } + +CACHE_TTL = int(os.getenv("CACHE_TTL",90)) + if not os.path.exists('hsdconfig.json'): with open('hsdconfig.json', 'w') as f: f.write(json.dumps(HSD_CONFIG, indent=4)) @@ -89,6 +96,13 @@ def hsdVersion(format=True): info = hsd.getInfo() if 'error' in info: return -1 + + # Check if SPV mode is enabled + global SPV_MODE + if info.get('chain',{}).get('options',{}).get('spv',False): + SPV_MODE = True + else: + SPV_MODE = False if format: return float('.'.join(info['version'].split(".")[:2])) else: @@ -215,6 +229,124 @@ def selectWallet(account: str): "message": response['error']['message'] } } + + +def init_domain_db(): + """Initialize the SQLite database for domain cache.""" + os.makedirs('cache', exist_ok=True) + db_path = os.path.join('cache', 'domains.db') + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Create the domains table if it doesn't exist + cursor.execute(''' + CREATE TABLE IF NOT EXISTS domains ( + name TEXT PRIMARY KEY, + info TEXT, + last_updated INTEGER + ) + ''') + + conn.commit() + conn.close() + + +def getCachedDomains(): + """Get cached domain information from SQLite database.""" + init_domain_db() # Ensure DB exists + + db_path = os.path.join('cache', 'domains.db') + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row # This allows accessing columns by name + cursor = conn.cursor() + + # Get all domains from the database + cursor.execute('SELECT name, info, last_updated FROM domains') + rows = cursor.fetchall() + + # Convert to dictionary format + domain_cache = {} + for row in rows: + try: + domain_cache[row['name']] = json.loads(row['info']) + domain_cache[row['name']]['last_updated'] = row['last_updated'] + except json.JSONDecodeError: + print(f"Error parsing cached data for domain {row['name']}") + + conn.close() + return domain_cache + + +ACTIVE_DOMAIN_UPDATES = set() # Track domains being updated +DOMAIN_UPDATE_LOCK = threading.Lock() # For thread-safe access to ACTIVE_DOMAIN_UPDATES + +def update_domain_cache(domain_names: list): + """Fetch domain info and update the SQLite cache.""" + if not domain_names: + return + + # Filter out domains that are already being updated + domains_to_update = [] + with DOMAIN_UPDATE_LOCK: + for domain in domain_names: + if domain not in ACTIVE_DOMAIN_UPDATES: + ACTIVE_DOMAIN_UPDATES.add(domain) + domains_to_update.append(domain) + + if not domains_to_update: + # All requested domains are already being updated + return + + try: + # Initialize database + init_domain_db() + + db_path = os.path.join('cache', 'domains.db') + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + for domain_name in domains_to_update: + try: + # Get domain info from node + domain_info = getDomain(domain_name) + + if 'error' in domain_info or not domain_info.get('info'): + print(f"Failed to get info for domain {domain_name}: {domain_info.get('error', 'Unknown error')}", flush=True) + continue + + # Update or insert into database + now = int(time.time()) + serialized_info = json.dumps(domain_info) + + cursor.execute( + 'INSERT OR REPLACE INTO domains (name, info, last_updated) VALUES (?, ?, ?)', + (domain_name, serialized_info, now) + ) + + print(f"Updated cache for domain {domain_name}") + except Exception as e: + print(f"Error updating cache for domain {domain_name}: {str(e)}") + finally: + # Always remove from active set, even if there was an error + with DOMAIN_UPDATE_LOCK: + if domain_name in ACTIVE_DOMAIN_UPDATES: + ACTIVE_DOMAIN_UPDATES.remove(domain_name) + + # Commit all changes at once + conn.commit() + conn.close() + + except Exception as e: + print(f"Error updating domain cache: {str(e)}", flush=True) + # Make sure to clean up the active set on any exception + with DOMAIN_UPDATE_LOCK: + for domain in domains_to_update: + if domain in ACTIVE_DOMAIN_UPDATES: + ACTIVE_DOMAIN_UPDATES.remove(domain) + + print("Updated cache for domains") + def getBalance(account: str): # Get the total balance @@ -232,9 +364,66 @@ def getBalance(account: str): domains = getDomains(account) domainValue = 0 - for domain in domains: - if domain['state'] == "CLOSED": - domainValue += domain['value'] + domains_to_update = [] # Track domains that need cache updates + + if isSPV(): + # Initialize database if needed + init_domain_db() + + # Connect to the database directly for efficient querying + db_path = os.path.join('cache', 'domains.db') + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + now = int(time.time()) + cache_cutoff = now - (CACHE_TTL * 86400) # Cache TTL in days + + for domain in domains: + domain_name = domain['name'] + + # Check if domain is in cache and still fresh + cursor.execute( + 'SELECT info, last_updated FROM domains WHERE name = ?', + (domain_name,) + ) + row = cursor.fetchone() + + # Only add domain for update if: + # 1. Not in cache or stale + # 2. Not currently being updated by another thread + with DOMAIN_UPDATE_LOCK: + if (not row or row['last_updated'] < cache_cutoff) and domain_name not in ACTIVE_DOMAIN_UPDATES: + domains_to_update.append(domain_name) + continue + + # Use the cached info + try: + if row: # Make sure we have data + domain_info = json.loads(row['info']) + if domain_info.get('info', {}).get('state', "") == "CLOSED": + domainValue += domain_info.get('info', {}).get('value', 0) + except json.JSONDecodeError: + # Only add for update if not already being updated + with DOMAIN_UPDATE_LOCK: + if domain_name not in ACTIVE_DOMAIN_UPDATES: + domains_to_update.append(domain_name) + + conn.close() + else: + for domain in domains: + if domain['state'] == "CLOSED": + domainValue += domain['value'] + + # Start background thread to update cache for missing domains + if domains_to_update: + thread = threading.Thread( + target=update_domain_cache, + args=(domains_to_update,), + daemon=True + ) + thread.start() + total = total - (domainValue/1000000) locked = locked - (domainValue/1000000) @@ -533,17 +722,7 @@ def isOwnPrevout(account, prevout: dict): def getDomain(domain: str): - # Get the domain - response = hsd.rpc_getNameInfo(domain) - if response['error'] is not None: - return { - "error": { - "message": response['error']['message'] - } - } - - # If info is None grab from hsd.hns.au - if response['result'] is None or response['result'].get('info') is None: + if isSPV(): response = requests.get(f"https://hsd.hns.au/api/v1/name/{domain}").json() if 'error' in response: return { @@ -553,6 +732,15 @@ def getDomain(domain: str): } return response + # Get the domain + response = hsd.rpc_getNameInfo(domain) + if response['error'] is not None: + return { + "error": { + "message": response['error']['message'] + } + } + return response['result'] def isKnownDomain(domain: str) -> bool: @@ -561,7 +749,6 @@ def isKnownDomain(domain: str) -> bool: if response['error'] is not None: return False - # If info is None grab from hsd.hns.au if response['result'] is None or response['result'].get('info') is None: return False return True @@ -570,11 +757,8 @@ def getAddressFromCoin(coinhash: str, coinindex = 0): # Get the address from the hash response = requests.get(get_node_api_url(f"coin/{coinhash}/{coinindex}")) if response.status_code != 200: - # Try to get coin from hsd.hns.au - response = requests.get(f"https://hsd.hns.au/api/v1/coin/{coinhash}/{coinindex}") - if response.status_code != 200: - print(f"Error getting address from coin") - return "No Owner" + print(f"Error getting address from coin") + return "No Owner" data = response.json() if 'address' not in data: print(json.dumps(data, indent=4)) @@ -1501,10 +1685,20 @@ def generateReport(account, format="{name},{expiry},{value},{maxBid}"): def convertHNS(value: int): return value/1000000 +SPV_EXTERNAL_ROUTES = [ + "name", + "coin", + "tx", + "block" +] def get_node_api_url(path=''): """Construct a URL for the HSD node API.""" base_url = f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}" + if isSPV() and any(path.startswith(route) for route in SPV_EXTERNAL_ROUTES): + # If in SPV mode and the path is one of the external routes, use the external API + base_url = f"https://hsd.hns.au/api/v1" + if path: # Ensure path starts with a slash if it's not empty if not path.startswith('/'): @@ -1522,6 +1716,19 @@ def get_wallet_api_url(path=''): return f"{base_url}{path}" return base_url +def isSPV() -> bool: + global SPV_MODE + if SPV_MODE is None: + info = hsd.getInfo() + if 'error' in info: + return False + + # Check if SPV mode is enabled + if info.get('chain',{}).get('options',{}).get('spv',False): + SPV_MODE = True + else: + SPV_MODE = False + return SPV_MODE # region HSD Internal Node @@ -1610,6 +1817,7 @@ def hsdInit(): def hsdStart(): global HSD_PROCESS + global SPV_MODE if not HSD_INTERNAL_NODE: return @@ -1650,6 +1858,7 @@ def hsdStart(): cmd.append(f"--chain-migrate={chain_migrate}") if wallet_migrate: cmd.append(f"--wallet-migrate={wallet_migrate}") + SPV_MODE = spv if spv: cmd.append("--spv") From 1fd9987bf1fec52914ab4ed3ef93f4914b0f33fa Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Thu, 28 Aug 2025 17:13:23 +1000 Subject: [PATCH 6/9] feat: Upgrade tx page caches to a sqlite db --- account.py | 98 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/account.py b/account.py index 8c6df1d..1cb7994 100644 --- a/account.py +++ b/account.py @@ -488,40 +488,74 @@ def getDomains(account, own=True): return domains +def init_tx_page_db(): + """Initialize the SQLite database for transaction page cache.""" + os.makedirs('cache', exist_ok=True) + db_path = os.path.join('cache', 'tx_pages.db') + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Create the tx_pages table if it doesn't exist + cursor.execute(''' + CREATE TABLE IF NOT EXISTS tx_pages ( + account TEXT, + page_key TEXT, + txid TEXT, + timestamp INTEGER, + PRIMARY KEY (account, page_key) + ) + ''') + + conn.commit() + conn.close() def getPageTXCache(account, page, size=100): - page = f"{page}-{size}" - if not os.path.exists(f'cache'): - os.mkdir(f'cache') - - if not os.path.exists(f'cache/{account}_page.json'): - with open(f'cache/{account}_page.json', 'w') as f: - f.write('{}') - with open(f'cache/{account}_page.json') as f: - pageCache = json.load(f) - - if page in pageCache and pageCache[page]['time'] > int(time.time()) - cacheTime: - return pageCache[page]['txid'] + """Get cached transaction ID from SQLite database.""" + account = getxPub(account) + page_key = f"{page}-{size}" + + # Initialize database if needed + init_tx_page_db() + + db_path = os.path.join('cache', 'tx_pages.db') + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Query for the cached transaction ID + cursor.execute( + 'SELECT txid, timestamp FROM tx_pages WHERE account = ? AND page_key = ?', + (account, page_key) + ) + row = cursor.fetchone() + conn.close() + + if row and row[1] > int(time.time()) - cacheTime: + return row[0] # Return the cached txid return None - def pushPageTXCache(account, page, txid, size=100): - page = f"{page}-{size}" - if not os.path.exists(f'cache/{account}_page.json'): - with open(f'cache/{account}_page.json', 'w') as f: - f.write('{}') - with open(f'cache/{account}_page.json') as f: - pageCache = json.load(f) - - pageCache[page] = { - 'time': int(time.time()), - 'txid': txid - } - with open(f'cache/{account}_page.json', 'w') as f: - json.dump(pageCache, f, indent=4) - - return pageCache[page]['txid'] - + """Store transaction ID in SQLite database.""" + account = getxPub(account) + page_key = f"{page}-{size}" + + # Initialize database if needed + init_tx_page_db() + + db_path = os.path.join('cache', 'tx_pages.db') + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Insert or replace the transaction ID + cursor.execute( + 'INSERT OR REPLACE INTO tx_pages (account, page_key, txid, timestamp) VALUES (?, ?, ?, ?)', + (account, page_key, txid, int(time.time())) + ) + + conn.commit() + conn.close() + + return txid def getTXFromPage(account, page, size=100): if page == 1: @@ -1569,7 +1603,9 @@ def zapTXs(account): def getxPub(account): - account_name = check_account(account) + account_name = account + if account.count(":") > 0: + account_name = check_account(account) if account_name == False: return { @@ -1587,8 +1623,6 @@ def getxPub(account): } } return response['accountKey'] - - return response except Exception as e: return { "error": { From a36c69ecfcc9e404d0e6bfd159e3135c5ea3d538 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Thu, 28 Aug 2025 17:18:52 +1000 Subject: [PATCH 7/9] fix: Add SPV support for getDNS() --- account.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/account.py b/account.py index 1cb7994..4ff3ca3 100644 --- a/account.py +++ b/account.py @@ -774,7 +774,6 @@ def getDomain(domain: str): "message": response['error']['message'] } } - return response['result'] def isKnownDomain(domain: str) -> bool: @@ -817,6 +816,17 @@ def renewDomain(account, domain): def getDNS(domain: str): # Get the DNS + + if isSPV(): + response = requests.get(f"https://hsd.hns.au/api/v1/nameresource/{domain}") + if response.status_code != 200: + return { + "error": f"Error fetching DNS records: {response.status_code}" + } + response = response.json() + return response.get('records', []) + + response = hsd.rpc_getNameResource(domain) if response['error'] is not None: return { From 23e714fad810a8ddf63f2a3600408d9f75179826 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Thu, 28 Aug 2025 17:27:45 +1000 Subject: [PATCH 8/9] feat: Add additional node info to settings --- FireWalletBrowser.bsdesign | Bin 344794 -> 344848 bytes main.py | 12 +++++++++--- templates/settings.html | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/FireWalletBrowser.bsdesign b/FireWalletBrowser.bsdesign index 6eb424148cb738aaec6d8ff3e85ba6b4f3821490..a5780ec572ae9ca4d94a6683052d58b55cce957d 100644 GIT binary patch delta 35385 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%0D0kk-Q!~Mtky}eDJlx#^5BpK$7pl>U^+=| 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 MuFbE7jRo?*0G_)89smFU diff --git a/main.py b/main.py index 911a1ba..2076780 100644 --- a/main.py +++ b/main.py @@ -1148,15 +1148,20 @@ def settings(): if success == None: success = "" + if not os.path.exists(".git"): return render_template("settings.html", account=account, hsd_version=account_module.hsdVersion(False), - error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE) + error=error,success=success,version="Error", + internal=account_module.HSD_INTERNAL_NODE, + spv=account_module.isSPV()) info = gitinfo.get_git_info() if not info: return render_template("settings.html", account=account, hsd_version=account_module.hsdVersion(False), - error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE) + error=error,success=success,version="Error", + internal=account_module.HSD_INTERNAL_NODE, + spv=account_module.isSPV()) branch = info['refs'] if branch != "main": @@ -1171,7 +1176,8 @@ def settings(): version += ' (New version available)' return render_template("settings.html", account=account, hsd_version=account_module.hsdVersion(False), - error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE) + error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE, + spv=account_module.isSPV()) @app.route('/settings/') def settings_action(action): diff --git a/templates/settings.html b/templates/settings.html index e7cba1d..eb4b7f7 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -68,7 +68,7 @@

{{success}}

-

Node Settings

HSD Version: v{{hsd_version}} +

Node Settings

HSD Version: v{{hsd_version}}  Type: {% if internal %} Internal {% else %} Remote {% endif %} ({% if spv %}SPV{% else %}Full Node{% endif %})
Settings that affect all wallets
  • Rescan From b76b873036fec5d6e73f715524bc6bbb4379d5ae Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Thu, 28 Aug 2025 17:50:47 +1000 Subject: [PATCH 9/9] feat: Update readme --- README.md | 19 +++++++++++++++++++ account.py | 9 ++++----- grant.md | 45 --------------------------------------------- 3 files changed, 23 insertions(+), 50 deletions(-) delete mode 100644 grant.md diff --git a/README.md b/README.md index 9a3d2fe..27cb27a 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,25 @@ If you set INTERNAL_HSD=true in the .env file the wallet will start and manage i } ``` +Supported config options are: +```yaml +spv: true/false +prefix: path to hsd data directory +flags: list of additional flags to pass to hsd +version: version of hsd to use (used when installing HSD from source) +chainMigrate: (for users migrating from older versions of HSD) +walletMigrate: (for users migrating from older versions of HSD) +``` + +## Support the Project + +If you find FireWallet useful and would like to support its continued development, please consider making a donation. Your contributions help maintain the project and develop new features. + +HNS donations can be sent to: `hs1qh7uzytf2ftwkd9dmjjs7az9qfver5m7dd7x4ej` +Other donation options can be found at [my website](https://nathan.woodburn.au/donate) + +Thank you for your support! + ## Warnings - This is a work in progress and is not guaranteed to work diff --git a/account.py b/account.py index 4ff3ca3..1f9db87 100644 --- a/account.py +++ b/account.py @@ -64,7 +64,8 @@ HSD_CONFIG = { ] } -CACHE_TTL = int(os.getenv("CACHE_TTL",90)) +TX_CACHE_TTL = 3600 +DOMAIN_CACHE_TTL = int(os.getenv("CACHE_TTL",90)) if not os.path.exists('hsdconfig.json'): with open('hsdconfig.json', 'w') as f: @@ -78,8 +79,6 @@ else: hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT) hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT) -cacheTime = 3600 - # Verify the connection response = hsd.getInfo() @@ -377,7 +376,7 @@ def getBalance(account: str): cursor = conn.cursor() now = int(time.time()) - cache_cutoff = now - (CACHE_TTL * 86400) # Cache TTL in days + cache_cutoff = now - (DOMAIN_CACHE_TTL * 86400) # Cache TTL in days for domain in domains: domain_name = domain['name'] @@ -530,7 +529,7 @@ def getPageTXCache(account, page, size=100): row = cursor.fetchone() conn.close() - if row and row[1] > int(time.time()) - cacheTime: + if row and row[1] > int(time.time()) - TX_CACHE_TTL: return row[0] # Return the cached txid return None diff --git a/grant.md b/grant.md deleted file mode 100644 index ea0ae21..0000000 --- a/grant.md +++ /dev/null @@ -1,45 +0,0 @@ -What have you built previously? -- [HNSHosting](https://hnshosting.au) -- [ShakeCities](https://shakecities.com) -- [FireWallet](https://firewallet.au) -- [Git Profile](https://github.com/nathanwoodburn) - -Project summary -A Handshake wallet web ui. This will be a HSD wallet web ui that will allow users to manage their Handshake domains via a web interface. This will allow users to easily manage their domains without having to use the command line or bob. One benefit of this is that it will allow users to easily manage their domains from their mobile devices that don't have access to any HNS wallet. This could be done in a secure way by only allowing connections on local network devices (in addition to requiring the wallet credentials). - -Features: -- Login with HSD wallet name + password (by default don't show a list of wallets to login to as this could be a security risk) -- View account information in a dashboard - - Available balance - - Total balance - - Pending Transactions - - List of domains - - List of transactions -- Manage domains - - Transfer domains - - Finalize domains - - Edit domains - - Revoke domains (with a warning and requiring the account password) -- Manage wallet - - Send HNS - - Receive HNS -- Auctions - - View bids on domain - - Open auction - - Bid on auction - - Reveal bid - - Redeem bid - - Register domain - -Completion requirements: -- Basic functionality including - - View info - - Send/Receive HNS - - Manage domains - -After the initial version is completed I will be looking to add more features including the above mentioned features. - - -The initial version will be completed in 2-3 weeks with a fully fledged version released later as the features are developed and tested. - -You can contact me at handshake @ nathan.woodburn.au \ No newline at end of file