23 Commits

Author SHA1 Message Date
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
86e174c337 fix: Lint default plugins
All checks were successful
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 52s
Test Python Compatibility / Python-Compatibility (3.11) (push) Successful in 1m55s
Test Python Compatibility / Python-Compatibility (3.10) (push) Successful in 1m57s
Test Python Compatibility / Python-Compatibility (3.13) (push) Successful in 1m45s
2025-09-02 15:58:55 +10:00
e7b787c30b fix: Lint to follow ruff standards
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 45s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 44s
Test Python Compatibility / Python-Compatibility (3.11) (push) Failing after 22s
Test Python Compatibility / Python-Compatibility (3.13) (push) Failing after 22s
Test Python Compatibility / Python-Compatibility (3.10) (push) Failing after 1m37s
2025-09-02 15:55:45 +10:00
997828795a feat: Add ruff linting
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 43s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 46s
Test Python Compatibility / Python-Compatibility (3.11) (push) Failing after 1m40s
Test Python Compatibility / Python-Compatibility (3.10) (push) Failing after 1m44s
Test Python Compatibility / Python-Compatibility (3.13) (push) Failing after 1m40s
2025-09-02 15:48:21 +10:00
30de2d585e fix: Use single quote in sign message and reduce test versions
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 49s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 53s
Test Python Compatibility / Python-Compatibility (3.10) (push) Successful in 1m45s
Test Python Compatibility / Python-Compatibility (3.13) (push) Successful in 1m44s
Test Python Compatibility / Python-Compatibility (3.8) (push) Failing after 1m24s
2025-09-02 15:28:39 +10:00
56eabfc1fc feat: Add some inital tests
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 46s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 49s
Test Python Compatibility / Python-Compatibility (3.10) (push) Failing after 1m38s
Test Python Compatibility / Python-Compatibility (3.13) (push) Successful in 1m51s
Test Python Compatibility / Python-Compatibility (3.6) (push) Failing after 22s
Test Python Compatibility / Python-Compatibility (3.7) (push) Failing after 1m16s
Test Python Compatibility / Python-Compatibility (3.8) (push) Failing after 1m29s
Test Python Compatibility / Python-Compatibility (3.9) (push) Failing after 1m28s
2025-09-02 15:20:31 +10:00
e0f24267f5 test: Try a new container to run
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 57s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 1m0s
Test Python Compatibility / Python-Compatibility (3.10) (push) Failing after 3m23s
Test Python Compatibility / Python-Compatibility (3.13) (push) Failing after 3m27s
Test Python Compatibility / Python-Compatibility (3.6) (push) Failing after 23s
Test Python Compatibility / Python-Compatibility (3.7) (push) Failing after 1m19s
Test Python Compatibility / Python-Compatibility (3.8) (push) Failing after 1m42s
Test Python Compatibility / Python-Compatibility (3.9) (push) Failing after 1m35s
2025-09-02 15:10:23 +10:00
2d51882d20 fix: Specify python minor version number
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 49s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 48s
Test Python Compatibility / Python-Compatibility (3.10.18) (push) Failing after 19s
Test Python Compatibility / Python-Compatibility (3.13.7) (push) Failing after 19s
Test Python Compatibility / Python-Compatibility (3.6.15) (push) Failing after 21s
Test Python Compatibility / Python-Compatibility (3.7.17) (push) Failing after 20s
Test Python Compatibility / Python-Compatibility (3.8.18) (push) Failing after 20s
Test Python Compatibility / Python-Compatibility (3.9.23) (push) Failing after 19s
2025-09-02 15:00:40 +10:00
06b1eea9ef fix: Disable arm on testing workflow
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 52s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 52s
Test Python Compatibility / Python-Compatibility (3.10) (push) Failing after 1m2s
Test Python Compatibility / Python-Compatibility (3.6) (push) Failing after 1m1s
Test Python Compatibility / Python-Compatibility (3.7) (push) Failing after 22s
Test Python Compatibility / Python-Compatibility (3.8) (push) Failing after 21s
Test Python Compatibility / Python-Compatibility (3.9) (push) Failing after 18s
2025-09-02 14:55:40 +10:00
d483cfdcfd feat: Add testing CI workflow
Some checks failed
Test Python Compatibility / Python-Compatibility (3.6) (push) Failing after 45s
Test Python Compatibility / Python-Compatibility (3.10) (push) Failing after 47s
Test Python Compatibility / Python-Compatibility (3.7) (push) Failing after 11s
Test Python Compatibility / Python-Compatibility (3.8) (push) Failing after 9s
Test Python Compatibility / Python-Compatibility (3.9) (push) Failing after 7s
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 2m10s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 2m13s
2025-09-02 14:49:14 +10:00
46ed0173d3 fix: Remove broken label
All checks were successful
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Successful in 40s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Successful in 43s
2025-08-29 23:18:49 +10:00
9dd50d1292 fix: Use full image name in compose 2025-08-29 23:17:10 +10:00
53148f573e revert: Use old manual install method
All checks were successful
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 4m18s
2025-08-29 23:11:47 +10:00
e8f052e0d1 feat: Try using matrix
Some checks failed
Build Docker / Build Images (map[dockerfile:Dockerfile.hsd tag_suffix:-hsd target:hsd]) (push) Failing after 32s
Build Docker / Build Images (map[dockerfile:Dockerfile tag_suffix: target:default]) (push) Failing after 11m41s
2025-08-29 23:03:50 +10:00
7f450d620a revert: Use older checkout
Some checks failed
Build Docker / Build Image (push) Has been cancelled
Build Docker / Build Image with HSD (push) Has been cancelled
2025-08-29 22:55:32 +10:00
41a1bc743f feat: Try new syntax for gitea action
Some checks failed
Build Docker / Build Image (push) Failing after 17s
Build Docker / Build Image with HSD (push) Failing after 23s
2025-08-29 22:53:44 +10:00
30108e3bc5 fix: Extra backslash in CI/CD pipeline
All checks were successful
Build Docker / Build Image (push) Successful in 38s
Build Docker / Build Image with HSD (push) Successful in 50s
2025-08-29 22:42:59 +10:00
a2dc9f43e3 feat: Add docker support for inbuilt HSD
Some checks failed
Build Docker / Build Image with HSD (push) Failing after 36s
Build Docker / Build Image (push) Failing after 38s
2025-08-29 22:40:30 +10:00
1203719eac fix: Docker python version and fix reporting missing requirements
All checks were successful
Build Docker / Build Image (push) Successful in 2m58s
2025-08-29 13:28:12 +10:00
373a71f04d Merge pull request 'SPV support & add internal HSD node' (#4) from feat/internal_hsd into main
All checks were successful
Build Docker / Build Image (push) Successful in 2m13s
Reviewed-on: #4
2025-08-29 13:04:30 +10:00
20 changed files with 417 additions and 187 deletions

32
.dockerignore Normal file
View File

@@ -0,0 +1,32 @@
.env
.env*
__pycache__/
templates/assets/css/styles.min.css
ignore/
plugins/signatures.json
.venv/
user_data/
customPlugins/
cache/
build/
dist/
hsd/
hsd-data/
hsd.lock
hsdconfig.json
Dockerfile
Dockerfile.hsd
FireWalletBrowser.bsdesign
LICENSE.md
README.md
docker-compose.yml
example.env
plugins.md

View File

@@ -4,11 +4,22 @@ on:
push: push:
jobs: jobs:
Build Image: Build Images:
runs-on: [ubuntu-latest, amd] runs-on: [ubuntu-latest, amd]
strategy:
matrix:
variant:
- target: default
tag_suffix: ""
dockerfile: "Dockerfile"
- target: hsd
tag_suffix: "-hsd"
dockerfile: "Dockerfile.hsd"
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Install Docker - name: Install Docker
run : | run : |
apt-get install ca-certificates curl gnupg apt-get install ca-certificates curl gnupg
@@ -17,7 +28,7 @@ jobs:
chmod a+r /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update apt-get update
apt-get install docker-ce-cli -y apt-get install docker-ce-cli -y
- name: Build Docker image - name: Build Docker image
run : | run : |
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
@@ -34,8 +45,8 @@ jobs:
fi fi
docker build -t firewallet:$tag_num . docker build --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VCS_REF=$GITEA_SHA --build-arg VERSION=$GITEA_TAG --file ${{matrix.variant.dockerfile}} -t firewallet${{matrix.variant.tag_suffix}}:$tag_num .
docker tag firewallet:$tag_num git.woodburn.au/nathanwoodburn/firewallet:$tag_num docker tag firewallet${{matrix.variant.tag_suffix}}:$tag_num git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag_num
docker push git.woodburn.au/nathanwoodburn/firewallet:$tag_num docker push git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag_num
docker tag firewallet:$tag_num git.woodburn.au/nathanwoodburn/firewallet:$tag docker tag firewallet${{matrix.variant.tag_suffix}}:$tag_num git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag
docker push git.woodburn.au/nathanwoodburn/firewallet:$tag docker push git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag

40
.gitea/workflows/test.yml Normal file
View File

@@ -0,0 +1,40 @@
name: Tests and Linting
run-name: Python Compatibility and Linting tests
on:
push:
jobs:
Tests-Linting:
runs-on: [ubuntu-latest, amd]
container: catthehacker/ubuntu:act-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.13']
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then
pip install -r requirements.txt
fi
pip install pytest ruff
- name: Test with pytest
run: |
echo "Testing with Python ${{ matrix.python-version }}"
python -m pytest main.py
- name: Lint with ruff
run: |
echo "Linting with Python ${{ matrix.python-version }}"
ruff check

View File

@@ -1,7 +1,7 @@
FROM --platform=$BUILDPLATFORM python:3.10-alpine AS builder FROM --platform=$BUILDPLATFORM python:3.13-alpine AS builder
WORKDIR /app WORKDIR /app
RUN apk add git openssl curl
COPY requirements.txt /app COPY requirements.txt /app
RUN --mount=type=cache,target=/root/.cache/pip \ RUN --mount=type=cache,target=/root/.cache/pip \
pip3 install -r requirements.txt pip3 install -r requirements.txt
@@ -9,10 +9,21 @@ RUN --mount=type=cache,target=/root/.cache/pip \
COPY . /app COPY . /app
# Add mount point for data volume # Add mount point for data volume
# VOLUME /data VOLUME /app/user_data
RUN apk add git openssl curl
ARG BUILD_DATE
ARG VCS_REF
LABEL org.opencontainers.image.title="FireWallet" \
org.opencontainers.image.description="The Handshake Wallet That is Fire" \
org.opencontainers.image.url="https://firewallet.au" \
org.opencontainers.image.source="https://git.woodburn.au/nathanwoodburn/firewalletbrowser" \
org.opencontainers.image.version="2.0.0" \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.licenses="AGPL-3.0-only"
ENTRYPOINT ["python3"] ENTRYPOINT ["python3"]
CMD ["server.py"] CMD ["server.py"]
FROM builder as dev-envs FROM builder AS dev-envs

57
Dockerfile.hsd Normal file
View File

@@ -0,0 +1,57 @@
# ---- HSD build stage ----
FROM node:22-alpine AS hsd-build
WORKDIR /opt/hsd
RUN apk add --no-cache git bash unbound-dev gmp-dev g++ gcc make python3
RUN git clone --depth=1 --branch v8.0.0 https://github.com/handshake-org/hsd.git .
RUN npm install --omit=dev
# ---- Final stage ----
FROM python:3.13-alpine
WORKDIR /app
# Install runtime deps only
RUN apk add --no-cache unbound-dev gmp
# Copy node and npm from hsd-build stage
COPY --from=hsd-build /usr/local/bin/node /usr/local/bin/node
COPY --from=hsd-build /usr/local/lib/node_modules/npm /usr/local/lib/node
COPY --from=hsd-build /usr/local/bin/npm /usr/local/bin/npm
# Copy FireWallet dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy HSD from build stage
COPY --from=hsd-build /opt/hsd /app/hsd
# Copy FireWallet source
COPY . .
# Expose ports
EXPOSE 5000
# Optional HSD ports
# EXPOSE 12037
# EXPOSE 12039
ENV INTERNAL_HSD=true
ENV HSD_DOCKER_CONTAINER=true
ARG BUILD_DATE
ARG VCS_REF
LABEL org.opencontainers.image.title="FireWallet (HSD)" \
org.opencontainers.image.description="The Handshake Wallet That is Fire" \
org.opencontainers.image.url="https://firewallet.au" \
org.opencontainers.image.source="https://git.woodburn.au/nathanwoodburn/firewalletbrowser" \
org.opencontainers.image.version="2.0.0" \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.licenses="AGPL-3.0-only"
VOLUME ["/app/hsd-data", "/app/user_data"]
ENTRYPOINT ["python3"]
CMD ["server.py"]

Binary file not shown.

View File

@@ -13,7 +13,6 @@ import signal
import sys import sys
import threading import threading
import sqlite3 import sqlite3
from functools import wraps
dotenv.load_dotenv() dotenv.load_dotenv()
@@ -131,7 +130,7 @@ def check_password(cookie: str|None, password: str|None):
password = "" password = ""
account = check_account(cookie) account = check_account(cookie)
if account == False: if not account:
return False return False
# Check if the password is valid # Check if the password is valid
@@ -639,7 +638,7 @@ def check_address(address: str, allow_name: bool = True, return_address: bool =
return False return False
return 'Invalid address' return 'Invalid address'
if response['result']['isvalid'] == True: if response['result']['isvalid']:
if return_address: if return_address:
return address return address
return 'Valid address' return 'Valid address'
@@ -789,7 +788,7 @@ def getAddressFromCoin(coinhash: str, coinindex = 0):
# Get the address from the hash # Get the address from the hash
response = requests.get(get_node_api_url(f"coin/{coinhash}/{coinindex}")) response = requests.get(get_node_api_url(f"coin/{coinhash}/{coinindex}"))
if response.status_code != 200: if response.status_code != 200:
print(f"Error getting address from coin") print("Error getting address from coin")
return "No Owner" return "No Owner"
data = response.json() data = response.json()
if 'address' not in data: if 'address' not in data:
@@ -802,7 +801,7 @@ def renewDomain(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -835,7 +834,7 @@ def getDNS(domain: str):
return { return {
"error": "No DNS records" "error": "No DNS records"
} }
if response['result'] == None: if response['result'] is None:
return [] return []
if 'records' not in response['result']: if 'records' not in response['result']:
@@ -847,7 +846,7 @@ def setDNS(account, domain, records):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -919,7 +918,7 @@ def getNodeSync():
def getWalletStatus(): def getWalletStatus():
response = hsw.rpc_getWalletInfo() response = hsw.rpc_getWalletInfo()
if 'error' in response and response['error'] != None: if 'error' in response and response['error'] is not None:
return "Error" return "Error"
# return response # return response
@@ -968,7 +967,7 @@ def getPendingReveals(account):
if bid['name'] == domain['name']: if bid['name'] == domain['name']:
state_found = False state_found = False
for reveal in reveals: for reveal in reveals:
if reveal['own'] == True: if reveal['own']:
if bid['value'] == reveal['value']: if bid['value'] == reveal['value']:
state_found = True state_found = True
@@ -998,8 +997,8 @@ def getPendingRedeems(account, password):
pending.append(nameHash) pending.append(nameHash)
else: else:
pending.append(name['result']) pending.append(name['result'])
except: except Exception as e:
print("Failed to parse redeems") print(f"Failed to parse redeems: {str(e)}")
return pending return pending
@@ -1009,7 +1008,7 @@ def getPendingRegisters(account):
domains = getDomains(account, False) domains = getDomains(account, False)
pending = [] pending = []
for domain in domains: for domain in domains:
if domain['state'] == "CLOSED" and domain['registered'] == False: if domain['state'] == "CLOSED" and not domain['registered']:
for bid in bids: for bid in bids:
if bid['name'] == domain['name']: if bid['name'] == domain['name']:
if bid['value'] == domain['highest']: if bid['value'] == domain['highest']:
@@ -1027,9 +1026,9 @@ def getPendingFinalizes(account, password):
pending = [] pending = []
try: try:
for output in tx['outputs']: for output in tx['outputs']:
if type(output) != dict: if type(output) is not dict:
continue continue
if not 'covenant' in output: if 'covenant' not in output:
continue continue
if output['covenant'].get("type") != 10: if output['covenant'].get("type") != 10:
continue continue
@@ -1042,8 +1041,8 @@ def getPendingFinalizes(account, password):
pending.append(nameHash) pending.append(nameHash)
else: else:
pending.append(name['result']) pending.append(name['result'])
except: except Exception as e:
print("Failed to parse finalizes") print(f"Failed to parse finalizes: {str(e)}")
return pending return pending
@@ -1066,7 +1065,7 @@ def revealAuction(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1086,7 +1085,7 @@ def revealAll(account):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1120,7 +1119,7 @@ def redeemAll(account):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1152,9 +1151,8 @@ def redeemAll(account):
def registerAll(account): def registerAll(account):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1177,9 +1175,8 @@ def registerAll(account):
def finalizeAll(account): def finalizeAll(account):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1212,7 +1209,7 @@ def bid(account, domain, bid, blind):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1237,7 +1234,7 @@ def openAuction(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1259,7 +1256,7 @@ def transfer(account, domain, address):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1281,7 +1278,7 @@ def finalize(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1318,7 +1315,7 @@ def cancelTransfer(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1355,7 +1352,7 @@ def revoke(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1392,7 +1389,7 @@ def sendBatch(account, batch):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1441,7 +1438,7 @@ def createBatch(account, batch):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1585,12 +1582,10 @@ def resendTXs():
} }
def zapTXs(account): def zapTXs(account, age=1200):
age = 60 * 20 # 20 minutes
account_name = check_account(account) account_name = check_account(account)
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1616,7 +1611,7 @@ def getxPub(account):
if account.count(":") > 0: if account.count(":") > 0:
account_name = check_account(account) account_name = check_account(account)
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1644,7 +1639,7 @@ def signMessage(account, domain, message):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1684,6 +1679,7 @@ def verifyMessageWithName(domain, signature, message):
return response['result'] return response['result']
return False return False
except Exception as e: except Exception as e:
print(f"Error verifying message with name: {str(e)}")
return False return False
@@ -1694,6 +1690,7 @@ def verifyMessage(address, signature, message):
return response['result'] return response['result']
return False return False
except Exception as e: except Exception as e:
print(f"Error verifying message: {str(e)}")
return False return False
# endregion # endregion
@@ -1740,7 +1737,7 @@ def get_node_api_url(path=''):
base_url = f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}" 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 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 # 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" base_url = "https://hsd.hns.au/api/v1"
if path: if path:
# Ensure path starts with a slash if it's not empty # Ensure path starts with a slash if it's not empty
@@ -1784,32 +1781,39 @@ def checkPreRequisites() -> dict[str, bool]:
"git": False, "git": False,
"hsd": False "hsd": False
} }
try:
# Check if node is installed and get version
nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True,timeout=2)
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
except Exception:
pass
try:
# Check if npm is installed
npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True,timeout=2)
if npmSubprocess.returncode == 0:
major_version = int(npmSubprocess.stdout.strip().split('.')[0])
if major_version >= HSD_CONFIG.get("minNPMVersion", 8):
prerequisites["npm"] = True
except Exception:
pass
# Check if node is installed and get version try:
nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True) # Check if git is installed
if nodeSubprocess.returncode == 0: gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True,timeout=2)
major_version = int(nodeSubprocess.stdout.strip().lstrip('v').split('.')[0]) if gitSubprocess.returncode == 0:
if major_version >= HSD_CONFIG.get("minNodeVersion", 20): prerequisites["git"] = True
prerequisites["node"] = True except Exception:
pass
# 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 # Check if hsd is installed
if os.path.exists("./hsd/bin/hsd"): if os.path.exists("./hsd/bin/hsd"):
prerequisites["hsd"] = True prerequisites["hsd"] = True
return prerequisites return prerequisites
@@ -1818,11 +1822,23 @@ def checkPreRequisites() -> dict[str, bool]:
def hsdInit(): def hsdInit():
if not HSD_INTERNAL_NODE: if not HSD_INTERNAL_NODE:
return return
prerequisites = checkPreRequisites()
# Don't check prerequisites if HSD is included in a docker container
if os.getenv("HSD_DOCKER_CONTAINER", "false").lower() == "true":
prerequisites = {
"node": True,
"npm": True,
"git": True,
"hsd": True
}
else:
prerequisites = checkPreRequisites()
minNodeVersion = HSD_CONFIG.get("minNodeVersion", 20)
minNPMVersion = HSD_CONFIG.get("minNPMVersion", 8)
PREREQ_MESSAGES = { PREREQ_MESSAGES = {
"node": "Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})", "node": f"Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})",
"npm": "Install npm (version >= {minNPMVersion}) - usually comes with Node.js", "npm": f"Install npm (version >= {minNPMVersion}) - usually comes with Node.js",
"git": "Install Git from https://git-scm.com/downloads"} "git": "Install Git from https://git-scm.com/downloads"}
@@ -1831,8 +1847,10 @@ def hsdInit():
print("HSD Internal Node prerequisites not met:") print("HSD Internal Node prerequisites not met:")
for key, value in prerequisites.items(): for key, value in prerequisites.items():
if not value: if not value:
print(f" - {key} is missing or does not meet the version requirement.") print(f" - {key} is missing or does not meet the version requirement.",flush=True)
exit(1) if key in PREREQ_MESSAGES:
print(PREREQ_MESSAGES[key],flush=True)
exit(1)
return return
# Check if hsd is installed # Check if hsd is installed
@@ -1925,7 +1943,8 @@ def hsdStart():
try: try:
signal.signal(signal.SIGINT, lambda s, f: (hsdStop(), sys.exit(0))) signal.signal(signal.SIGINT, lambda s, f: (hsdStop(), sys.exit(0)))
signal.signal(signal.SIGTERM, lambda s, f: (hsdStop(), sys.exit(0))) signal.signal(signal.SIGTERM, lambda s, f: (hsdStop(), sys.exit(0)))
except: except Exception as e:
print(f"Failed to set signal handlers: {str(e)}")
pass pass
@@ -1958,7 +1977,6 @@ def hsdRestart():
hsdStart() hsdStart()
checkPreRequisites()
hsdInit() hsdInit()
hsdStart() hsdStart()
# endregion # endregion

View File

@@ -0,0 +1,14 @@
services:
firewallet:
image: git.woodburn.au/nathanwoodburn/firewallet-hsd:latest
ports:
- "5000:5000"
volumes:
- hsd_data:/app/hsd-data
- user_data:/app/user_data
environment:
- INTERNAL_HSD=true
volumes:
hsd_data:
user_data:

26
docker-compose.yml Normal file
View File

@@ -0,0 +1,26 @@
services:
hsd:
image: ghcr.io/handshake-org/hsd:8
volumes:
- hsd_data:/root/.hsd
environment:
- HSD_HTTP_HOST=0.0.0.0
- HSD_WALLET_HTTP_HOST=0.0.0.0
- HSD_LOG_LEVEL=error
- HSD_API_KEY=changeme
firewallet:
image: git.woodburn.au/nathanwoodburn/firewallet:latest
ports:
- "5000:5000"
environment:
- HSD_IP=hsd
- HSD_API=changeme
volumes:
- user_data:/app/user_data
volumes:
hsd_data:
user_data:

View File

@@ -11,7 +11,6 @@ import dns.query
import dns.rdatatype import dns.rdatatype
import httpx import httpx
from requests_doh import DNSOverHTTPSSession, add_dns_provider from requests_doh import DNSOverHTTPSSession, add_dns_provider
import requests
import urllib3 import urllib3
from cryptography.x509.oid import ExtensionOID from cryptography.x509.oid import ExtensionOID
@@ -172,11 +171,11 @@ def resolve_TLSA_with_doh(query_name, doh_url="https://hnsdoh.com/dns-query"):
def emoji_to_punycode(emoji): def emoji_to_punycode(emoji):
try: try:
return emoji.encode("idna").decode("ascii") return emoji.encode("idna").decode("ascii")
except Exception as e: except Exception:
return emoji return emoji
def punycode_to_emoji(punycode): def punycode_to_emoji(punycode):
try: try:
return punycode.encode("ascii").decode("idna") return punycode.encode("ascii").decode("idna")
except Exception as e: except Exception:
return punycode return punycode

136
main.py
View File

@@ -12,11 +12,9 @@ import re
from flask_qrcode import QRcode from flask_qrcode import QRcode
import domainLookup import domainLookup
import urllib.parse import urllib.parse
import importlib
import plugin as plugins_module import plugin as plugins_module
import gitinfo import gitinfo
import datetime import datetime
import functools
import time import time
dotenv.load_dotenv() dotenv.load_dotenv()
@@ -30,7 +28,7 @@ fees = 0.02
revokeCheck = random.randint(100000,999999) revokeCheck = random.randint(100000,999999)
THEME = os.getenv("THEME") THEME = os.getenv("THEME", "black")
def blocks_to_time(blocks: int) -> str: def blocks_to_time(blocks: int) -> str:
@@ -114,7 +112,7 @@ def transactions():
page = request.args.get('page', 1) page = request.args.get('page', 1)
try: try:
page = int(page) page = int(page)
except: except ValueError:
page = 1 page = 1
if page < 1: if page < 1:
@@ -196,7 +194,7 @@ def send():
content = f"Are you sure you want to send {amount} HNS to {toAddress}<br><br>" content = f"Are you sure you want to send {amount} HNS to {toAddress}<br><br>"
content += f"This will cost {amount} HNS + mining fees and is not able to be undone." content += f"This will cost {amount} HNS + mining fees and is not able to be undone."
cancel = f"/send" cancel = "/send"
confirm = f"/send/confirm?address={address}&amount={amount}" confirm = f"/send/confirm?address={address}&amount={amount}"
@@ -211,7 +209,7 @@ def sendConfirmed():
address = request.args.get("address") address = request.args.get("address")
amount = float(request.args.get("amount","0")) amount = float(request.args.get("amount","0"))
response = account_module.send(request.cookies.get("account"),address,amount) response = account_module.send(request.cookies.get("account"),address,amount)
if 'error' in response and response['error'] != None: if 'error' in response and response['error'] is not None:
# If error is a dict get the message # If error is a dict get the message
if isinstance(response['error'], dict): if isinstance(response['error'], dict):
if 'message' in response['error']: if 'message' in response['error']:
@@ -282,7 +280,7 @@ def auctions():
# Sort # Sort
sort = request.args.get("sort") sort = request.args.get("sort")
if sort == None: if sort is None:
sort = "time" sort = "time"
sort = sort.lower() sort = sort.lower()
sort_price = "" sort_price = ""
@@ -296,7 +294,7 @@ def auctions():
reverse = False reverse = False
direction = request.args.get("direction") direction = request.args.get("direction")
if direction == None: if direction is None:
if sort == "time": if sort == "time":
direction = "" direction = ""
else: else:
@@ -365,7 +363,7 @@ def revealAllBids():
return redirect("/auctions?message=Failed to reveal bids") return redirect("/auctions?message=Failed to reveal bids")
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] is not None:
if response['error']['message'] == "Nothing to do.": if response['error']['message'] == "Nothing to do.":
return redirect("/auctions?message=No reveals pending") return redirect("/auctions?message=No reveals pending")
return redirect("/auctions?message=" + response['error']['message']) return redirect("/auctions?message=" + response['error']['message'])
@@ -388,7 +386,7 @@ def redeemAllBids():
return redirect("/auctions?message=Failed to redeem bids") return redirect("/auctions?message=Failed to redeem bids")
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] is not None:
if response['error']['message'] == "Nothing to do.": if response['error']['message'] == "Nothing to do.":
return redirect("/auctions?message=No redeems pending") return redirect("/auctions?message=No redeems pending")
return redirect("/auctions?message=" + response['error']['message']) return redirect("/auctions?message=" + response['error']['message'])
@@ -410,7 +408,7 @@ def registerAllDomains():
return redirect("/auctions?message=Failed to register domains") return redirect("/auctions?message=Failed to register domains")
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] is not None:
if response['error']['message'] == "Nothing to do.": if response['error']['message'] == "Nothing to do.":
return redirect("/auctions?message=No domains to register") return redirect("/auctions?message=No domains to register")
return redirect("/auctions?message=" + response['error']['message']) return redirect("/auctions?message=" + response['error']['message'])
@@ -429,7 +427,7 @@ def finalizeAllBids():
response = account_module.finalizeAll(request.cookies.get("account")) response = account_module.finalizeAll(request.cookies.get("account"))
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] is not None:
if response['error']['message'] == "Nothing to do.": if response['error']['message'] == "Nothing to do.":
return redirect("/dashboard?message=No domains to finalize") return redirect("/dashboard?message=No domains to finalize")
return redirect("/dashboard?message=" + response['error']['message']) return redirect("/dashboard?message=" + response['error']['message'])
@@ -507,7 +505,6 @@ def search():
domain_info = account_module.getDomain(search_term) domain_info = account_module.getDomain(search_term)
owner = 'Unknown' owner = 'Unknown'
dns = [] dns = []
txs = []
if domain_info: if domain_info:
# Check if info and info.owner # Check if info and info.owner
@@ -560,10 +557,10 @@ def manage(domain: str):
dns = render.dns(dns) dns = render.dns(dns)
errorMessage = request.args.get("error") errorMessage = request.args.get("error")
if errorMessage == None: if errorMessage is None:
errorMessage = "" errorMessage = ""
address = request.args.get("address") address = request.args.get("address")
if address == None: if address is None:
address = "" address = ""
finalize_time = "" finalize_time = ""
@@ -607,7 +604,7 @@ def finalize(domain: str):
domain = domain.lower() domain = domain.lower()
response = account_module.finalize(request.cookies.get("account"),domain) response = account_module.finalize(request.cookies.get("account"),domain)
if response['error'] != None: if response['error'] is not None:
print(response) print(response)
return redirect("/manage/" + domain + "?error=" + response['error']['message']) return redirect("/manage/" + domain + "?error=" + response['error']['message'])
@@ -626,7 +623,7 @@ def cancelTransfer(domain: str):
domain = domain.lower() domain = domain.lower()
response = account_module.cancelTransfer(request.cookies.get("account"),domain) response = account_module.cancelTransfer(request.cookies.get("account"),domain)
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] is not None:
print(response) print(response)
return redirect("/manage/" + domain + "?error=" + response['error']['message']) return redirect("/manage/" + domain + "?error=" + response['error']['message'])
@@ -645,9 +642,9 @@ def revokeInit(domain: str):
domain = domain.lower() domain = domain.lower()
content = f"Are you sure you want to revoke {domain}/?<br>" content = f"Are you sure you want to revoke {domain}/?<br>"
content += f"This will return the domain to the auction pool and you will lose any funds spent on the domain.<br>" content += "This will return the domain to the auction pool and you will lose any funds spent on the domain.<br>"
content += f"This cannot be undone after the transaction is sent.<br><br>" content += "This cannot be undone after the transaction is sent.<br><br>"
content += f"Please enter your password to confirm." content += "Please enter your password to confirm."
cancel = f"/manage/{domain}" cancel = f"/manage/{domain}"
confirm = f"/manage/{domain}/revoke/confirm" confirm = f"/manage/{domain}/revoke/confirm"
@@ -676,13 +673,13 @@ def revokeConfirm(domain: str):
return redirect("/manage/" + domain + "?error=An error occurred. Please try again.") return redirect("/manage/" + domain + "?error=An error occurred. Please try again.")
response = account_module.check_password(request.cookies.get("account"),password) response = account_module.check_password(request.cookies.get("account"),password)
if response == False: if not response:
return redirect("/manage/" + domain + "?error=Invalid password") return redirect("/manage/" + domain + "?error=Invalid password")
response = account_module.revoke(request.cookies.get("account"),domain) response = account_module.revoke(request.cookies.get("account"),domain)
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] is not None:
print(response) print(response)
return redirect("/manage/" + domain + "?error=" + response['error']['message']) return redirect("/manage/" + domain + "?error=" + response['error']['message'])
@@ -722,7 +719,7 @@ def editPage(domain: str):
user_edits = request.args.get("dns") user_edits = request.args.get("dns")
if user_edits != None: if user_edits is not None:
dns = urllib.parse.unquote(user_edits) dns = urllib.parse.unquote(user_edits)
else: else:
dns = account_module.getDNS(domain) dns = account_module.getDNS(domain)
@@ -735,7 +732,7 @@ def editPage(domain: str):
# Check if new records have been added # Check if new records have been added
dnsType = request.args.get("type") dnsType = request.args.get("type")
dnsValue = request.args.get("value") dnsValue = request.args.get("value")
if dnsType != None and dnsValue != None: if dnsType is not None and dnsValue is not None:
if dnsType != "DS": if dnsType != "DS":
dns.append({"type": dnsType, "value": dnsValue}) dns.append({"type": dnsType, "value": dnsValue})
else: else:
@@ -749,7 +746,7 @@ def editPage(domain: str):
key_tag = int(ds[0]) key_tag = int(ds[0])
algorithm = int(ds[1]) algorithm = int(ds[1])
digest_type = int(ds[2]) digest_type = int(ds[2])
except: except ValueError:
raw_dns = str(dns).replace("'",'"') raw_dns = str(dns).replace("'",'"')
return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record") return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record")
@@ -761,7 +758,7 @@ def editPage(domain: str):
raw_dns = str(dns).replace("'",'"') raw_dns = str(dns).replace("'",'"')
dns = render.dns(dns,True) dns = render.dns(dns,True)
errorMessage = request.args.get("error") errorMessage = request.args.get("error")
if errorMessage == None: if errorMessage is None:
errorMessage = "" errorMessage = ""
@@ -820,7 +817,7 @@ def transfer(domain):
action = f"Send {domain}/ to {request.form.get('address')}" action = f"Send {domain}/ to {request.form.get('address')}"
content = f"Are you sure you want to send {domain}/ to {toAddress}<br><br>" content = f"Are you sure you want to send {domain}/ to {toAddress}<br><br>"
content += f"This requires sending a finalize transaction 2 days after the transfer is initiated." content += "This requires sending a finalize transaction 2 days after the transfer is initiated."
cancel = f"/manage/{domain}?address={address}" cancel = f"/manage/{domain}?address={address}"
confirm = f"/manage/{domain}/transfer/confirm?address={address}" confirm = f"/manage/{domain}/transfer/confirm?address={address}"
@@ -847,9 +844,9 @@ def signMessage(domain):
content = "Message to sign:<br><code>" + message + "</code><br><br>" content = "Message to sign:<br><code>" + message + "</code><br><br>"
signedMessage = account_module.signMessage(request.cookies.get("account"),domain,message) signedMessage = account_module.signMessage(request.cookies.get("account"),domain,message)
if signedMessage["error"] != None: if signedMessage["error"] is not None:
return redirect("/manage/" + domain + "?error=" + signedMessage["error"]) return redirect("/manage/" + domain + "?error=" + signedMessage["error"])
content += f"Signature:<br><code>{signedMessage["result"]}</code><br><br>" content += f"Signature:<br><code>{signedMessage['result']}</code><br><br>"
data = { data = {
"domain": domain, "domain": domain,
@@ -906,7 +903,7 @@ def auction(domain):
domainInfo = account_module.getDomain(search_term) domainInfo = account_module.getDomain(search_term)
error = request.args.get("error") error = request.args.get("error")
if error == None: if error is None:
error = "" error = ""
if 'error' in domainInfo: if 'error' in domainInfo:
@@ -916,9 +913,9 @@ def auction(domain):
error=error) error=error)
if domainInfo['info'] is None: if domainInfo['info'] is None:
if 'registered' in domainInfo and domainInfo['registered'] == False and 'expired' in domainInfo and domainInfo['expired'] == False: if 'registered' in domainInfo and not domainInfo['registered'] and 'expired' in domainInfo and not domainInfo['expired']:
# Needs to be registered # Needs to be registered
next_action = f'ERROR GETTING NEXT STATE' next_action = 'ERROR GETTING NEXT STATE'
else: else:
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>' next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
return render_template("auction.html", account=account, return render_template("auction.html", account=account,
@@ -965,7 +962,7 @@ def auction(domain):
elif stats['blocksUntilReveal'] == 2: elif stats['blocksUntilReveal'] == 2:
next += "<br>LAST CHANCE TO BID" next += "<br>LAST CHANCE TO BID"
elif stats['blocksUntilReveal'] == 3: elif stats['blocksUntilReveal'] == 3:
next += f"<br>Next block is last chance to bid" next += "<br>Next block is last chance to bid"
elif stats['blocksUntilReveal'] < 6: elif stats['blocksUntilReveal'] < 6:
next += f"<br>Last chance to bid in {stats['blocksUntilReveal']-2} blocks" next += f"<br>Last chance to bid in {stats['blocksUntilReveal']-2} blocks"
@@ -997,7 +994,7 @@ def rescan_auction(domain):
domain = domain.lower() domain = domain.lower()
response = account_module.rescan_auction(account,domain) account_module.rescan_auction(account,domain)
return redirect("/auction/" + domain) return redirect("/auction/" + domain)
@app.route('/auction/<domain>/bid') @app.route('/auction/<domain>/bid')
@@ -1093,7 +1090,7 @@ def open_auction(domain):
response = account_module.openAuction(request.cookies.get("account"),domain) response = account_module.openAuction(request.cookies.get("account"),domain)
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] is not None:
return redirect("/auction/" + domain + "?error=" + response['error']['message']) return redirect("/auction/" + domain + "?error=" + response['error']['message'])
return redirect(f"/success?tx={response['hash']}") return redirect(f"/success?tx={response['hash']}")
@@ -1142,10 +1139,10 @@ def settings():
return redirect("/logout") return redirect("/logout")
error = request.args.get("error") error = request.args.get("error")
if error == None: if error is None:
error = "" error = ""
success = request.args.get("success") success = request.args.get("success")
if success == None: if success is None:
success = "" success = ""
@@ -1203,8 +1200,16 @@ def settings_action(action):
if action == "zap": if action == "zap":
resp = account_module.zapTXs(request.cookies.get("account")) age = request.args.get("age", 1200)
if type(resp) == dict and 'error' in resp: 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?error=" + str(resp['error']))
return redirect("/settings?success=Zapped transactions") return redirect("/settings?success=Zapped transactions")
@@ -1224,12 +1229,24 @@ def settings_action(action):
return render_template("message.html", account=account, return render_template("message.html", account=account,
title="Restarting", title="Restarting",
content="The node is restarting. This may take a minute or two. You can close this window.") 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") return redirect("/settings?error=Invalid action")
@app.route('/settings/upload', methods=['POST']) @app.route('/settings/upload', methods=['POST'])
def upload_image(): def upload_image():
if not 'account' in request.cookies: if 'account' not in request.cookies:
return redirect("/login?message=Not logged in") return redirect("/login?message=Not logged in")
account = request.cookies.get("account") account = request.cookies.get("account")
@@ -1253,7 +1270,7 @@ def upload_image():
return redirect("/settings?error=An error occurred") return redirect("/settings?error=An error occurred")
def latestVersion(branch): def latestVersion(branch):
result = requests.get(f"https://git.woodburn.au/api/v1/repos/nathanwoodburn/firewalletbrowser/branches") result = requests.get("https://git.woodburn.au/api/v1/repos/nathanwoodburn/firewalletbrowser/branches")
if result.status_code != 200: if result.status_code != 200:
return "Error" return "Error"
@@ -1290,7 +1307,7 @@ def login_post():
account = request.form.get("account") account = request.form.get("account")
password = request.form.get("password") password = request.form.get("password")
if account == None or password == None: if account is None or password is None:
wallets = account_module.listWallets() wallets = account_module.listWallets()
wallets = render.wallets(wallets) wallets = render.wallets(wallets)
return render_template("login.html", return render_template("login.html",
@@ -1329,7 +1346,7 @@ def register():
password = request.form.get("password") password = request.form.get("password")
repeatPassword = request.form.get("password_repeat") repeatPassword = request.form.get("password_repeat")
if account == None or password == None or repeatPassword == None: if account is None or password is None or repeatPassword is None:
return render_template("register.html", return render_template("register.html",
error="Invalid account or password", error="Invalid account or password",
name=account,password=password,password_repeat=repeatPassword) name=account,password=password,password_repeat=repeatPassword)
@@ -1376,7 +1393,7 @@ def import_wallet():
repeatPassword = request.form.get("password_repeat") repeatPassword = request.form.get("password_repeat")
seed = request.form.get("seed") seed = request.form.get("seed")
if account == None or password == None or repeatPassword == None or seed == None: if account is None or password is None or repeatPassword is None or seed is None:
return render_template("import-wallet.html", return render_template("import-wallet.html",
error="Invalid account, password or seed", error="Invalid account, password or seed",
name=account,password=password,password_repeat=repeatPassword, name=account,password=password,password_repeat=repeatPassword,
@@ -1475,12 +1492,12 @@ def plugin(ptype,plugin):
functions = plugins_module.getPluginFunctions(plugin) functions = plugins_module.getPluginFunctions(plugin)
functions = render.plugin_functions(functions,plugin) functions = render.plugin_functions(functions,plugin)
if data['verified'] == False: if not data['verified']:
functions = "<div class='container-fluid'><div class='alert alert-warning' role='alert'>This plugin is not verified and is disabled for your protection. Please check the code before marking the plugin as verified <a href='/plugin/" + plugin + "/verify' class='btn btn-danger'>Verify</a></div></div>" + functions functions = "<div class='container-fluid'><div class='alert alert-warning' role='alert'>This plugin is not verified and is disabled for your protection. Please check the code before marking the plugin as verified <a href='/plugin/" + plugin + "/verify' class='btn btn-danger'>Verify</a></div></div>" + functions
error = request.args.get("error") error = request.args.get("error")
if error == None: if error is None:
error = "" error = ""
return render_template("plugin.html", account=account, return render_template("plugin.html", account=account,
@@ -1506,7 +1523,7 @@ def plugin_verify(ptype,plugin):
data = plugins_module.getPluginData(plugin) data = plugins_module.getPluginData(plugin)
if data['verified'] == False: if not data['verified']:
plugins_module.verifyPlugin(plugin) plugins_module.verifyPlugin(plugin)
return redirect("/plugin/" + plugin) return redirect("/plugin/" + plugin)
@@ -1593,7 +1610,7 @@ def api_hsd(function):
if not domain: if not domain:
return jsonify({"error": "No domain specified"}), 400 return jsonify({"error": "No domain specified"}), 400
domainInfo = account_module.getDomain(domain) domainInfo = account_module.getDomain(domain)
if 'error' in domainInfo and domainInfo['error'] != None: if 'error' in domainInfo and domainInfo['error'] is not None:
return jsonify({"error": domainInfo['error']}), 400 return jsonify({"error": domainInfo['error']}), 400
stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {} stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
state = domainInfo['info']['state'] state = domainInfo['info']['state']
@@ -1628,7 +1645,7 @@ def api_hsd(function):
elif stats['blocksUntilReveal'] == 2: elif stats['blocksUntilReveal'] == 2:
next += "<br>LAST CHANCE TO BID" next += "<br>LAST CHANCE TO BID"
elif stats['blocksUntilReveal'] == 3: elif stats['blocksUntilReveal'] == 3:
next += f"<br>Next block is last chance to bid" next += "<br>Next block is last chance to bid"
elif stats['blocksUntilReveal'] < 6: elif stats['blocksUntilReveal'] < 6:
next += f"<br>Last chance to bid in {stats['blocksUntilReveal']-2} blocks" next += f"<br>Last chance to bid in {stats['blocksUntilReveal']-2} blocks"
@@ -1714,7 +1731,7 @@ def api_wallet(function):
if function == "domains": if function == "domains":
domains = account_module.getDomains(account) domains = account_module.getDomains(account)
if type(domains) == dict and 'error' in domains: if type(domains) is dict and 'error' in domains:
return jsonify({"result": [], "error": domains['error']}) return jsonify({"result": [], "error": domains['error']})
# Add nameRender to each domain # Add nameRender to each domain
@@ -1728,7 +1745,7 @@ def api_wallet(function):
page = request.args.get('page', 1) page = request.args.get('page', 1)
try: try:
page = int(page) page = int(page)
except: except ValueError:
page = 1 page = 1
if page < 1: if page < 1:
@@ -1783,9 +1800,9 @@ def api_wallet(function):
if function == "icon": if function == "icon":
# Check if there is an icon # Check if there is an icon
if not os.path.exists(f'user_data/images'): if not os.path.exists('user_data/images'):
return send_file('templates/assets/img/HNS.png') return send_file('templates/assets/img/HNS.png')
files = os.listdir(f'user_data/images') files = os.listdir('user_data/images')
for file in files: for file in files:
if file.startswith(account): if file.startswith(account):
return send_file(f'user_data/images/{file}') return send_file(f'user_data/images/{file}')
@@ -1801,7 +1818,6 @@ def api_wallet_mobile(function):
return jsonify({"error": "Not logged in"}) return jsonify({"error": "Not logged in"})
account = account_module.check_account(request.cookies.get("account")) account = account_module.check_account(request.cookies.get("account"))
password = request.cookies.get("account","").split(":")[1]
if not account: if not account:
return jsonify({"error": "Invalid account"}) return jsonify({"error": "Invalid account"})
@@ -1820,9 +1836,9 @@ def api_wallet_mobile(function):
@app.route('/api/v1/icon/<account>') @app.route('/api/v1/icon/<account>')
def api_icon(account): def api_icon(account):
if not os.path.exists(f'user_data/images'): if not os.path.exists('user_data/images'):
return send_file('templates/assets/img/HNS.png') return send_file('templates/assets/img/HNS.png')
files = os.listdir(f'user_data/images') files = os.listdir('user_data/images')
for file in files: for file in files:
if file.startswith(account): if file.startswith(account):
return send_file(f'user_data/images/{file}') return send_file(f'user_data/images/{file}')
@@ -1854,7 +1870,7 @@ def renderDomain(name: str) -> str:
return f"{rendered}/ ({name})" return f"{rendered}/ ({name})"
except Exception as e: except Exception:
return f"{name}/" return f"{name}/"
#endregion #endregion
@@ -1906,3 +1922,9 @@ if __name__ == '__main__':
app.run(debug=True) app.run(debug=True)
else: else:
app.run() app.run()
def tests():
assert blocks_to_time(6) == "1 hrs"
assert blocks_to_time(3) == "30 mins"
assert blocks_to_time(1) == "10 mins"
assert blocks_to_time(10) == "1 hrs 40 mins"

View File

@@ -62,7 +62,8 @@ def listPlugins(update=False):
try: try:
with open("user_data/plugin_signatures.json", "r") as f: with open("user_data/plugin_signatures.json", "r") as f:
signatures = json.load(f) signatures = json.load(f)
except: except Exception as e:
print(f"Error loading plugin signatures: {e}")
# Write a new signatures file # Write a new signatures file
with open("user_data/plugin_signatures.json", "w") as f: with open("user_data/plugin_signatures.json", "w") as f:
json.dump(signatures, f) json.dump(signatures, f)
@@ -87,7 +88,8 @@ def verifyPlugin(plugin: str):
try: try:
with open("user_data/plugin_signatures.json", "r") as f: with open("user_data/plugin_signatures.json", "r") as f:
signatures = json.load(f) signatures = json.load(f)
except: except Exception as e:
print(f"Error loading plugin signatures: {e}")
# Write a new signatures file # Write a new signatures file
with open("user_data/plugin_signatures.json", "w") as f: with open("user_data/plugin_signatures.json", "w") as f:
json.dump(signatures, f) json.dump(signatures, f)
@@ -120,7 +122,8 @@ def getPluginData(pluginStr: str):
try: try:
with open("user_data/plugin_signatures.json", "r") as f: with open("user_data/plugin_signatures.json", "r") as f:
signatures = json.load(f) signatures = json.load(f)
except: except Exception as e:
print(f"Error loading plugin signatures: {e}")
# Write a new signatures file # Write a new signatures file
with open("user_data/plugin_signatures.json", "w") as f: with open("user_data/plugin_signatures.json", "w") as f:
json.dump(signatures, f) json.dump(signatures, f)
@@ -171,7 +174,8 @@ def runPluginFunction(plugin: str, function: str, params: dict, authentication:
try: try:
with open("user_data/plugin_signatures.json", "r") as f: with open("user_data/plugin_signatures.json", "r") as f:
signatures = json.load(f) signatures = json.load(f)
except: except Exception as e:
print(f"Error loading plugin signatures: {e}")
# Write a new signatures file # Write a new signatures file
with open("user_data/plugin_signatures.json", "w") as f: with open("user_data/plugin_signatures.json", "w") as f:
json.dump(signatures, f) json.dump(signatures, f)

View File

@@ -1,4 +1,3 @@
import json
import account import account
import requests import requests
import threading import threading
@@ -127,7 +126,7 @@ def automations_background(authentication):
account_name = account.check_account(authentication) account_name = account.check_account(authentication)
password = ":".join(authentication.split(":")[1:]) password = ":".join(authentication.split(":")[1:])
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"

View File

@@ -1,7 +1,5 @@
import json
import account import account
import requests import requests
import os
@@ -384,7 +382,7 @@ def bid(params, authentication):
bid = float(params["bid"]) bid = float(params["bid"])
blind = float(params["blind"]) blind = float(params["blind"])
blind+=bid blind+=bid
except: except ValueError:
return { return {
"status":"Invalid bid amount", "status":"Invalid bid amount",
"transaction":None "transaction":None

View File

@@ -1,6 +1,4 @@
import json import json
import account
import requests
import os import os
# Plugin Data # Plugin Data

View File

@@ -1,7 +1,5 @@
import json
import account import account
import requests import requests
import os
# Plugin Data # Plugin Data
info = { info = {
@@ -90,7 +88,7 @@ def main(params, authentication):
return {"status": f"Failed: {batch['error']['message']}", "transaction": "None"} return {"status": f"Failed: {batch['error']['message']}", "transaction": "None"}
if 'result' in batch: if 'result' in batch:
if batch['result'] != None: if batch['result'] is not None:
tx = batch['result']['hash'] tx = batch['result']['hash']
return {"status": "Success", "transaction": tx} return {"status": "Success", "transaction": tx}
# Note only one batch can be sent at a time # Note only one batch can be sent at a time

View File

@@ -93,7 +93,7 @@ def status(params, authentication):
response = requests.post(f"https://{instance}/api", json=data, headers=headers) response = requests.post(f"https://{instance}/api", json=data, headers=headers)
if response.status_code != 200: if response.status_code != 200:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
if response.json()["success"] != True: if not response.json()["success"]:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
return {"status": f"Connected to {instance}"} return {"status": f"Connected to {instance}"}
@@ -110,7 +110,7 @@ def login(params, authentication):
if response.status_code != 200: if response.status_code != 200:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
if response.json()["success"] != True: if not response.json()["success"]:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
auth = { auth = {
@@ -146,7 +146,7 @@ def addDomain(params, authentication):
zones = requests.post(f"https://{instance}/api", json=data, headers=headers) zones = requests.post(f"https://{instance}/api", json=data, headers=headers)
if zones.status_code != 200: if zones.status_code != 200:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
if zones.json()["success"] != True: if not zones.json()["success"]:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
zones = zones.json()["data"] zones = zones.json()["data"]
@@ -169,7 +169,7 @@ def addDomain(params, authentication):
response = requests.post(f"https://{instance}/api", json=data, headers=headers) response = requests.post(f"https://{instance}/api", json=data, headers=headers)
if response.status_code != 200: if response.status_code != 200:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
if response.json()["success"] != True: if not response.json()["success"]:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
zoneID = response.json()["data"]["zone"] zoneID = response.json()["data"]["zone"]
data = { data = {
@@ -179,7 +179,7 @@ def addDomain(params, authentication):
response = requests.post(f"https://{instance}/api", json=data, headers=headers) response = requests.post(f"https://{instance}/api", json=data, headers=headers)
if response.status_code != 200: if response.status_code != 200:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
if response.json()["success"] != True: if not response.json()["success"]:
return {"status": "Error connecting to Varo"} return {"status": "Error connecting to Varo"}
zone = response.json()["data"] zone = response.json()["data"]

View File

@@ -2,9 +2,7 @@ import datetime
import json import json
import urllib.parse import urllib.parse
from flask import render_template from flask import render_template
from domainLookup import punycode_to_emoji
import os import os
from handywrapper import api
import threading import threading
import requests import requests
@@ -55,7 +53,7 @@ def domains(domains, mobile=False):
link = f'/manage/{domain["name"]}' link = f'/manage/{domain["name"]}'
link_action = "Manage" link_action = "Manage"
if domain['registered'] == False: if not domain['registered']:
link_action = "Register" link_action = "Register"
link = f'/auction/{domain["name"]}/register' link = f'/auction/{domain["name"]}/register'
@@ -182,7 +180,7 @@ def transactions(txs):
elif amount > 0: elif amount > 0:
amount = f"<span style='color: green;'>+{amount:,.2f}</span>" amount = f"<span style='color: green;'>+{amount:,.2f}</span>"
else: else:
amount = f"<span style='color: gray;'>0.00</span>" amount = "<span style='color: gray;'>0.00</span>"
# hash = f"<a target='_blank' href='{TX_EXPLORER_URL}{hash}'>{hash[:8]}...</a>" # hash = f"<a target='_blank' href='{TX_EXPLORER_URL}{hash}'>{hash[:8]}...</a>"
@@ -253,7 +251,7 @@ def txs(data):
amount = entry['amount'] amount = entry['amount']
amount = amount / 1000000 amount = amount / 1000000
if entry['blind'] == None: if entry['blind'] is None:
html_output += f"<td>{amount:,.2f} HNS</td>\n" html_output += f"<td>{amount:,.2f} HNS</td>\n"
else: else:
blind = entry['blind'] blind = entry['blind']
@@ -261,7 +259,7 @@ def txs(data):
html_output += f"<td>{amount:,.2f} + {blind:,.2f} HNS</td>\n" html_output += f"<td>{amount:,.2f} + {blind:,.2f} HNS</td>\n"
html_output += f"<td>{timestamp_to_readable_time(entry['time'])}</td>\n" html_output += f"<td>{timestamp_to_readable_time(entry['time'])}</td>\n"
html_output += f"</tr>\n" html_output += "</tr>\n"
return html_output return html_output
@@ -316,13 +314,13 @@ def bids(bids,reveals):
html += f"<td>{value:,.2f} HNS</td>" html += f"<td>{value:,.2f} HNS</td>"
html += f"<td>{bidValue:,.2f} HNS</td>" html += f"<td>{bidValue:,.2f} HNS</td>"
else: else:
html += f"<td>Hidden until reveal</td>" html += "<td>Hidden until reveal</td>"
html += f"<td>Hidden until reveal</td>" html += "<td>Hidden until reveal</td>"
if bid['own']: if bid['own']:
html += "<td>You</td>" html += "<td>You</td>"
else: else:
html += f"<td>Unknown</td>" html += "<td>Unknown</td>"
html += f"<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' target='_blank' href='{TX_EXPLORER_URL}{bid['prevout']['hash']}'>Bid TX 🔗</a></td>" html += f"<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' target='_blank' href='{TX_EXPLORER_URL}{bid['prevout']['hash']}'>Bid TX 🔗</a></td>"
html += "</tr>" html += "</tr>"
@@ -410,22 +408,22 @@ def plugin_functions(functions, pluginName):
functionType = functions[function]["type"] functionType = functions[function]["type"]
html += f'<div class="card" style="margin-top: 50px;">' html += '<div class="card" style="margin-top: 50px;">'
html += f'<div class="card-body">' html += '<div class="card-body">'
html += f'<h4 class="card-title">{name}</h4>' html += f'<h4 class="card-title">{name}</h4>'
html += f'<h6 class="text-muted card-subtitle mb-2">{description}</h6>' html += f'<h6 class="text-muted card-subtitle mb-2">{description}</h6>'
html += f'<h6 class="text-muted card-subtitle mb-2">Function type: {functionType.capitalize()}</h6>' html += f'<h6 class="text-muted card-subtitle mb-2">Function type: {functionType.capitalize()}</h6>'
if functionType != "default": if functionType != "default":
html += f'<p class="card-text">Returns: {returns}</p>' html += f'<p class="card-text">Returns: {returns}</p>'
html += f'</div>' html += '</div>'
html += f'</div>' html += '</div>'
continue continue
# Form # Form
html += f'<form method="post" style="padding: 20px;" action="/plugin/{pluginName}/{function}">' html += f'<form method="post" style="padding: 20px;" action="/plugin/{pluginName}/{function}">'
for param in params: for param in params:
html += f'<div style="margin-bottom: 20px;">' html += '<div style="margin-bottom: 20px;">'
paramName = params[param]["name"] paramName = params[param]["name"]
paramType = params[param]["type"] paramType = params[param]["type"]
if paramType == "text": if paramType == "text":
@@ -449,14 +447,14 @@ def plugin_functions(functions, pluginName):
html += f'</div>' html += '</div>'
html += f'<button type="submit" class="btn btn-primary">Submit</button>' html += '<button type="submit" class="btn btn-primary">Submit</button>'
html += f'</form>' html += '</form>'
# For debugging # For debugging
html += f'<p class="card-text">Returns: {returns}</p>' html += f'<p class="card-text">Returns: {returns}</p>'
html += f'</div>' html += '</div>'
html += f'</div>' html += '</div>'
return html return html
@@ -468,16 +466,16 @@ def plugin_output(outputs, returns):
for returnOutput in returns: for returnOutput in returns:
if returnOutput not in outputs: if returnOutput not in outputs:
continue continue
html += f'<div class="card" style="margin-top: 50px; margin-bottom: 50px;">' html += '<div class="card" style="margin-top: 50px; margin-bottom: 50px;">'
html += f'<div class="card-body">' html += '<div class="card-body">'
html += f'<h4 class="card-title">{returns[returnOutput]["name"]}</h4>' html += f'<h4 class="card-title">{returns[returnOutput]["name"]}</h4>'
output = outputs[returnOutput] output = outputs[returnOutput]
if returns[returnOutput]["type"] == "list": if returns[returnOutput]["type"] == "list":
html += f'<ul>' html += '<ul>'
for item in output: for item in output:
html += f'<li>{item}</li>' html += f'<li>{item}</li>'
html += f'</ul>' html += '</ul>'
elif returns[returnOutput]["type"] == "text": elif returns[returnOutput]["type"] == "text":
html += f'<p>{output}</p>' html += f'<p>{output}</p>'
elif returns[returnOutput]["type"] == "tx": elif returns[returnOutput]["type"] == "tx":
@@ -487,8 +485,8 @@ def plugin_output(outputs, returns):
html += render_template('components/dns-output.html', dns=dns(output)) html += render_template('components/dns-output.html', dns=dns(output))
html += f'</div>' html += '</div>'
html += f'</div>' html += '</div>'
return html return html
def plugin_output_dash(outputs, returns): def plugin_output_dash(outputs, returns):
@@ -498,7 +496,7 @@ def plugin_output_dash(outputs, returns):
for returnOutput in returns: for returnOutput in returns:
if returnOutput not in outputs: if returnOutput not in outputs:
continue continue
if outputs[returnOutput] == None: if outputs[returnOutput] is None:
continue continue
html += render_template('components/dashboard-plugin.html', name=returns[returnOutput]["name"], output=outputs[returnOutput]) html += render_template('components/dashboard-plugin.html', name=returns[returnOutput]["name"], output=outputs[returnOutput])
return html return html
@@ -517,7 +515,7 @@ def renderDomain(name: str) -> str:
return f"{rendered}/ ({name})" return f"{rendered}/ ({name})"
except Exception as e: except Exception:
return f"{name}/" return f"{name}/"
def renderDomainAsync(namehash: str) -> None: def renderDomainAsync(namehash: str) -> None:

View File

@@ -1,4 +1,3 @@
import os
import sys import sys
import platform import platform
from main import app from main import app
@@ -39,6 +38,6 @@ if __name__ == '__main__':
sys.exit() sys.exit()
print(f'Starting server with Waitress on {platform.system()} with {threads} threads...', flush=True) print(f'Starting server with Waitress on {platform.system()} with {threads} threads...', flush=True)
print(f'Press Ctrl+C to stop the server', flush=True) print('Press Ctrl+C to stop the server', flush=True)
print(f'Serving on http://0.0.0.0:5000/', flush=True) print('Serving on http://0.0.0.0:5000/', flush=True)
serve(app, host="0.0.0.0", port=5000, threads=threads) serve(app, host="0.0.0.0", port=5000, threads=threads)

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> <h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
</div> </div>
</li> </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 %} {% if internal %}
<li class="list-group-item"> <li class="list-group-item">
<div><a class="btn btn-primary stick-right" role="button" href="/settings/restart">Restart Node</a> <div><a class="btn btn-primary stick-right" role="button" href="/settings/restart">Restart Node</a>