14 Commits

Author SHA1 Message Date
59000afa87 feat: Cleanup function layouts for alerts
All checks were successful
Tests and Linting / Tests-Linting (3.11) (push) Successful in 35s
Tests and Linting / Tests-Linting (3.10) (push) Successful in 40s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 37s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 51s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 50s
2025-09-09 17:34:36 +10:00
699a74f093 feat: Add api route to dismiss alert
All checks were successful
Tests and Linting / Tests-Linting (3.11) (push) Successful in 29s
Tests and Linting / Tests-Linting (3.10) (push) Successful in 36s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 37s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 42s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 47s
2025-09-09 17:29:11 +10:00
6096f82c4d feat: Add an alert framework
All checks were successful
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 3m6s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 3m9s
Tests and Linting / Tests-Linting (3.10) (push) Successful in 3m18s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 3m17s
Tests and Linting / Tests-Linting (3.11) (push) Successful in 3m25s
2025-09-09 17:24:39 +10:00
4353eb8fa4 feat: Use new proxied script url
All checks were successful
Tests and Linting / Tests-Linting (3.10) (push) Successful in 32s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 33s
Tests and Linting / Tests-Linting (3.11) (push) Successful in 38s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 48s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 54s
2025-09-04 22:05:16 +10:00
344cde07d0 feat: Add more info about the installation script to readme
All checks were successful
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 1m4s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 1m12s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 1m48s
Tests and Linting / Tests-Linting (3.10) (push) Successful in 1m52s
Tests and Linting / Tests-Linting (3.11) (push) Successful in 3m51s
2025-09-04 21:36:30 +10:00
2fb841aeaf fix: Update link for installation script to use raw
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Has been cancelled
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Has been cancelled
Tests and Linting / Tests-Linting (3.10) (push) Has been cancelled
Tests and Linting / Tests-Linting (3.13) (push) Has been cancelled
Tests and Linting / Tests-Linting (3.11) (push) Has been cancelled
2025-09-04 21:34:19 +10:00
60df317f78 feat: Add install script to readme
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Has started running
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Has been cancelled
Tests and Linting / Tests-Linting (3.11) (push) Has been cancelled
Tests and Linting / Tests-Linting (3.10) (push) Has been cancelled
Tests and Linting / Tests-Linting (3.13) (push) Has been cancelled
2025-09-04 21:33:28 +10:00
4c1ea9fb12 feat: Add installation and start scripts 2025-09-04 21:29:47 +10:00
58ed636ce3 fix: Use git --version instead of short -v to allow for backwards compatability
The default git installed in ubuntu doesn't allow -v
2025-09-04 21:29:05 +10:00
e537c323c2 fix: Update paths to be consistent
All checks were successful
Tests and Linting / Tests-Linting (3.11) (push) Successful in 32s
Tests and Linting / Tests-Linting (3.10) (push) Successful in 43s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 45s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 51s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 59s
2025-09-02 18:15:56 +10:00
812fc84d3e feat: Allow zapping txs of a different age
All checks were successful
Tests and Linting / Tests-Linting (3.11) (push) Successful in 31s
Tests and Linting / Tests-Linting (3.10) (push) Successful in 34s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 35s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 42s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 45s
2025-09-02 17:58:40 +10:00
6d318a597b feat: Add API info to settings page
This is most useful when using an internal node with a random api key
2025-09-02 17:54:56 +10:00
83bd6b9643 feat: Reword workflow
All checks were successful
Tests and Linting / Tests-Linting (3.11) (push) Successful in 36s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 50s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 1m10s
Tests and Linting / Tests-Linting (3.10) (push) Successful in 3m15s
Tests and Linting / Tests-Linting (3.13) (push) Successful in 3m17s
2025-09-02 17:23:13 +10:00
c93b2652f5 Merge pull request 'Add CI workflow to test code with older versions of python' (#6) from feat/ci-testing into main
All checks were successful
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 3m7s
Test Python Compatibility / Python-Compatibility (3.10) (push) Successful in 4m59s
Test Python Compatibility / Python-Compatibility (3.11) (push) Successful in 4m57s
Test Python Compatibility / Python-Compatibility (3.13) (push) Successful in 1m58s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 7m3s
Reviewed-on: #6
2025-09-02 16:08:35 +10:00
11 changed files with 252 additions and 20 deletions

View File

@@ -1,10 +1,10 @@
name: Test Python Compatibility
run-name: Test Python Compatibility
name: Tests and Linting
run-name: Python Compatibility and Linting tests
on:
push:
jobs:
Python-Compatibility:
Tests-Linting:
runs-on: [ubuntu-latest, amd]
container: catthehacker/ubuntu:act-latest
strategy:
@@ -29,7 +29,7 @@ jobs:
fi
pip install pytest ruff
- name: Run tests
- name: Test with pytest
run: |
echo "Testing with Python ${{ matrix.python-version }}"
python -m pytest main.py

2
.gitignore vendored
View File

@@ -17,6 +17,6 @@ cache/
build/
dist/
hsd/
hsd-data/
hsd_data/
hsd.lock
hsdconfig.json

View File

@@ -50,7 +50,7 @@ LABEL org.opencontainers.image.title="FireWallet (HSD)" \
VOLUME ["/app/hsd-data", "/app/user_data"]
VOLUME ["/app/hsd_data", "/app/user_data"]
ENTRYPOINT ["python3"]

Binary file not shown.

View File

@@ -13,6 +13,17 @@ cp example.env .env
Edit .env to have your HSD api key.
If you have HSD runnning on a separate computer also add the IP here
For a quick and easy installation on ubuntu/debian you can run the install.sh script
```bash
curl https://firewallet.au/install.sh | bash
```
This will install all dependencies (including Node/NPM for an internal HSD node), create a python virtual environment and install the required python packages.
After the script has run you can start the wallet with
```bash
./start.sh
```
## Usage
Make sure HSD is running then run the following commands:

View File

@@ -1582,9 +1582,7 @@ def resendTXs():
}
def zapTXs(account):
age = 60 * 20 # 20 minutes
def zapTXs(account, age=1200):
account_name = check_account(account)
if not account_name:
@@ -1806,7 +1804,7 @@ def checkPreRequisites() -> dict[str, bool]:
try:
# Check if git is installed
gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True,timeout=2)
gitSubprocess = subprocess.run(["git", "--version"], capture_output=True, text=True,timeout=2)
if gitSubprocess.returncode == 0:
prerequisites["git"] = True
except Exception:
@@ -1902,7 +1900,7 @@ 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"))
prefix = HSD_CONFIG.get("prefix", os.path.join(os.getcwd(), "hsd_data"))
# Base command
@@ -1978,7 +1976,6 @@ def hsdRestart():
time.sleep(2)
hsdStart()
hsdInit()
hsdStart()
# endregion

View File

@@ -4,10 +4,8 @@ services:
ports:
- "5000:5000"
volumes:
- hsd_data:/app/hsd-data
- hsd_data:/app/hsd_data
- user_data:/app/user_data
environment:
- INTERNAL_HSD=true
volumes:
hsd_data:

81
install.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
install_command=""
# Check if currently in the FireWalletBrowser directory
if [ -f "server.py" ]; then
echo "Please run this script from outside the FireWalletBrowser directory."
exit 1
fi
echo "Starting installation of FireWalletBrowser..."
# Check if OS is using apt package manager (Debian/Ubuntu)
if command -v apt-get &> /dev/null; then
install_command="sudo apt-get install -y"
dependencies=(git curl wget python3 python3-pip python3-venv)
echo "Detected apt package manager."
# Check if OS is using pacman package manager (Arch Linux)
elif command -v pacman &> /dev/null; then
install_command="sudo pacman -S"
dependencies=(git curl wget python3 python-pip)
echo "Detected pacman package manager."
else
echo "Unsupported package manager. Please install dependencies manually."
exit 1
fi
# List of dependencies to install
# Install dependencies
for package in "${dependencies[@]}"; do
# Check if the package is already installed
if command -v $package &> /dev/null || dpkg -s $package &> /dev/null || pacman -Qi $package &> /dev/null; then
echo "$package is already installed."
continue
fi
echo "Installing $package..."
$install_command $package
# Check if the installation was successful
if [ $? -ne 0 ]; then
echo "Failed to install $package. Please check your package manager settings."
exit 1
fi
done
if ! command -v node &> /dev/null || ! command -v npm &> /dev/null; then
echo "Installing Node.js and npm..."
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"
nvm install 20
if [ $? -ne 0 ]; then
echo "Failed to install Node.js and npm. Please install them manually."
exit 1
fi
else
echo "Node.js and npm are already installed."
fi
# Clone repo
git clone https://git.woodburn.au/nathanwoodburn/firewalletbrowser.git
# Setup venv
cd firewalletbrowser || exit 1
python3 -m venv .venv
source .venv/bin/activate
# Install python dependencies
python3 -m pip install -r requirements.txt
# Write .env file
if [ ! -f ".env" ]; then
echo "Creating .env file..."
echo "INTERNAL_HSD=true" > .env
echo "Created .env file with INTERNAL Node enabled."
fi
echo "Installation complete. You can start the application by running ./start.sh"

134
main.py
View File

@@ -1,6 +1,7 @@
import io
import json
import random
import sqlite3
import sys
from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory,send_file
import os
@@ -84,6 +85,13 @@ def index():
print("New version available",flush=True)
plugins += render_template('components/dashboard-alert.html', name='Update', output='A new version of FireWallet is available')
alerts = get_alerts(account)
for alert in alerts:
output_html = alert['output']
# Add a dismiss button
output_html += f"&nbsp<a href='/dismiss/{alert['id']}' class='btn btn-secondary btn-sm' style='margin:none;'>Dismiss</a>"
plugins += render_template('components/dashboard-alert.html', name=alert['name'], output=output_html)
return render_template("index.html", account=account, plugins=plugins)
def reverseDirection(direction: str):
@@ -1200,7 +1208,15 @@ def settings_action(action):
if action == "zap":
resp = account_module.zapTXs(request.cookies.get("account"))
age = request.args.get("age", 1200)
try:
age = int(age)
except ValueError:
age = 1200
if age < 0:
age = 1200
resp = account_module.zapTXs(request.cookies.get("account"),age)
if type(resp) is dict and 'error' in resp:
return redirect("/settings?error=" + str(resp['error']))
return redirect("/settings?success=Zapped transactions")
@@ -1222,6 +1238,18 @@ def settings_action(action):
title="Restarting",
content="The node is restarting. This may take a minute or two. You can close this window.")
if action == "api-info":
content = f"API URL: <code>http://{account_module.HSD_IP}:{account_module.HSD_NODE_PORT}</code><br>"
content += f"Wallet URL: <code>http://{account_module.HSD_IP}:{account_module.HSD_WALLET_PORT}</code><br>"
content += f"API Key: <code>{account_module.HSD_API}</code><br><br>"
return render_template("message.html", account=account,
title="API Information",
content=content)
return redirect("/settings?error=Invalid action")
@app.route('/settings/upload', methods=['POST'])
@@ -1410,7 +1438,7 @@ def import_wallet():
name=account,password=password,password_repeat=repeatPassword,
seed=seed)
add_alert("Rescan needed", "Please rescan the wallet after importing to see all transactions", account)
# Set the cookie
response = make_response(redirect("/"))
response.set_cookie("account", account+":"+password)
@@ -1853,6 +1881,108 @@ def renderDomain(name: str) -> str:
except Exception:
return f"{name}/"
def get_alerts(account:str) -> list:
"""
Get alerts to show on the dashboard.
"""
alerts = []
# Check if the node is connected
if not account_module.hsdConnected():
alerts.append({
"name": "Node",
"output": "HSD node is not connected. Please check your settings."
})
return alerts
# Check if the wallet is synced
wallet_status = account_module.getWalletStatus()
if wallet_status != "Ready":
alerts.append({
"name": "Wallet",
"output": f"The wallet is not synced ({wallet_status}). Please wait for it to sync."
})
print(account)
# Try to read from notifications sqlite database
if os.path.exists("user_data/notifications.db"):
try:
conn = sqlite3.connect("user_data/notifications.db")
c = conn.cursor()
c.execute("SELECT id, name, message FROM notifications WHERE read=0 AND (account=? OR account='all')", (account,))
rows = c.fetchall()
for row in rows:
alerts.append({
"id": row[0],
"name": row[1],
"output": row[2]
})
conn.close()
except Exception as e:
print(f"Error reading notifications: {e}",flush=True)
pass
return alerts
def add_alert(name:str,output:str,account:str="all"):
"""
Add an alert to the notifications database.
name: Name of the alert
output: Message of the alert
account: Account to add the alert for (default: all)
"""
if not os.path.exists("user_data/notifications.db"):
conn = sqlite3.connect("user_data/notifications.db")
c = conn.cursor()
c.execute("CREATE TABLE notifications (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, message TEXT, account TEXT, read INTEGER DEFAULT 0)")
conn.commit()
conn.close()
try:
conn = sqlite3.connect("user_data/notifications.db")
c = conn.cursor()
c.execute("INSERT INTO notifications (name, message, account) VALUES (?, ?, ?)", (name, output, account))
conn.commit()
conn.close()
except Exception as e:
print(f"Error adding notification: {e}",flush=True)
pass
def dismiss_alert(alert_id:int,account:str="all"):
"""
Mark an alert as read.
alert_id: ID of the alert to dismiss
account: Account to dismiss the alert for (default: all)
"""
if not os.path.exists("user_data/notifications.db"):
return
try:
conn = sqlite3.connect("user_data/notifications.db")
c = conn.cursor()
c.execute("UPDATE notifications SET read=1 WHERE id=?", (alert_id,))
conn.commit()
conn.close()
except Exception as e:
print(f"Error dismissing notification: {e}",flush=True)
pass
@app.route('/dismiss/<int:alert_id>')
@app.route('/api/v1/dismiss/<int:alert_id>')
def dismiss_alert_route(alert_id):
# Check if the user is logged in
if request.cookies.get("account") is None:
return redirect("/login")
account = account_module.check_account(request.cookies.get("account"))
if not account:
return redirect("/logout")
dismiss_alert(alert_id,account)
return redirect(request.referrer or "/")
#endregion

9
start.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Find if .venv exists
if [ -d ".venv" ]; then
echo "Virtual environment found. Activating..."
source .venv/bin/activate
fi
python3 server.py

View File

@@ -85,6 +85,12 @@
<h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
</div>
</li>
<li class="list-group-item">
<div><a class="btn btn-primary stick-right" role="button" href="/settings/api-info">API Info</a>
<h3>View API Information</h3><span>View information about the connected HSD node&#39;s API</span>
</div>
</li>
{% if internal %}
<li class="list-group-item">
<div><a class="btn btn-primary stick-right" role="button" href="/settings/restart">Restart Node</a>