23 Commits

Author SHA1 Message Date
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
b76b873036 feat: Update readme
All checks were successful
Build Docker / Build Image (push) Successful in 54s
2025-08-28 17:50:47 +10:00
23e714fad8 feat: Add additional node info to settings
All checks were successful
Build Docker / Build Image (push) Successful in 45s
2025-08-28 17:27:45 +10:00
a36c69ecfc fix: Add SPV support for getDNS()
All checks were successful
Build Docker / Build Image (push) Successful in 55s
2025-08-28 17:18:52 +10:00
1fd9987bf1 feat: Upgrade tx page caches to a sqlite db 2025-08-28 17:13:23 +10:00
22 changed files with 498 additions and 269 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: Test Python Compatibility
run-name: Test Python Compatibility
on:
push:
jobs:
Python-Compatibility:
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: Run tests
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

@@ -143,6 +143,25 @@ If you set INTERNAL_HSD=true in the .env file the wallet will start and manage i
} }
``` ```
Supported config options are:
```yaml
spv: true/false
prefix: path to hsd data directory
flags: list of additional flags to pass to hsd
version: version of hsd to use (used when installing HSD from source)
chainMigrate: <int> (for users migrating from older versions of HSD)
walletMigrate: <int> (for users migrating from older versions of HSD)
```
## Support the Project
If you find FireWallet useful and would like to support its continued development, please consider making a donation. Your contributions help maintain the project and develop new features.
HNS donations can be sent to: `hs1qh7uzytf2ftwkd9dmjjs7az9qfver5m7dd7x4ej`
Other donation options can be found at [my website](https://nathan.woodburn.au/donate)
Thank you for your support!
## Warnings ## Warnings
- This is a work in progress and is not guaranteed to work - This is a work in progress and is not guaranteed to work

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()
@@ -64,7 +63,8 @@ HSD_CONFIG = {
] ]
} }
CACHE_TTL = int(os.getenv("CACHE_TTL",90)) TX_CACHE_TTL = 3600
DOMAIN_CACHE_TTL = int(os.getenv("CACHE_TTL",90))
if not os.path.exists('hsdconfig.json'): if not os.path.exists('hsdconfig.json'):
with open('hsdconfig.json', 'w') as f: with open('hsdconfig.json', 'w') as f:
@@ -78,8 +78,6 @@ else:
hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT) hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT) hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT)
cacheTime = 3600
# Verify the connection # Verify the connection
response = hsd.getInfo() response = hsd.getInfo()
@@ -132,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
@@ -377,7 +375,7 @@ def getBalance(account: str):
cursor = conn.cursor() cursor = conn.cursor()
now = int(time.time()) now = int(time.time())
cache_cutoff = now - (CACHE_TTL * 86400) # Cache TTL in days cache_cutoff = now - (DOMAIN_CACHE_TTL * 86400) # Cache TTL in days
for domain in domains: for domain in domains:
domain_name = domain['name'] domain_name = domain['name']
@@ -488,40 +486,74 @@ def getDomains(account, own=True):
return domains return domains
def init_tx_page_db():
"""Initialize the SQLite database for transaction page cache."""
os.makedirs('cache', exist_ok=True)
db_path = os.path.join('cache', 'tx_pages.db')
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create the tx_pages table if it doesn't exist
cursor.execute('''
CREATE TABLE IF NOT EXISTS tx_pages (
account TEXT,
page_key TEXT,
txid TEXT,
timestamp INTEGER,
PRIMARY KEY (account, page_key)
)
''')
conn.commit()
conn.close()
def getPageTXCache(account, page, size=100): def getPageTXCache(account, page, size=100):
page = f"{page}-{size}" """Get cached transaction ID from SQLite database."""
if not os.path.exists(f'cache'): account = getxPub(account)
os.mkdir(f'cache') page_key = f"{page}-{size}"
if not os.path.exists(f'cache/{account}_page.json'): # Initialize database if needed
with open(f'cache/{account}_page.json', 'w') as f: init_tx_page_db()
f.write('{}')
with open(f'cache/{account}_page.json') as f: db_path = os.path.join('cache', 'tx_pages.db')
pageCache = json.load(f) conn = sqlite3.connect(db_path)
cursor = conn.cursor()
if page in pageCache and pageCache[page]['time'] > int(time.time()) - cacheTime:
return pageCache[page]['txid'] # Query for the cached transaction ID
cursor.execute(
'SELECT txid, timestamp FROM tx_pages WHERE account = ? AND page_key = ?',
(account, page_key)
)
row = cursor.fetchone()
conn.close()
if row and row[1] > int(time.time()) - TX_CACHE_TTL:
return row[0] # Return the cached txid
return None return None
def pushPageTXCache(account, page, txid, size=100): def pushPageTXCache(account, page, txid, size=100):
page = f"{page}-{size}" """Store transaction ID in SQLite database."""
if not os.path.exists(f'cache/{account}_page.json'): account = getxPub(account)
with open(f'cache/{account}_page.json', 'w') as f: page_key = f"{page}-{size}"
f.write('{}')
with open(f'cache/{account}_page.json') as f: # Initialize database if needed
pageCache = json.load(f) init_tx_page_db()
pageCache[page] = { db_path = os.path.join('cache', 'tx_pages.db')
'time': int(time.time()), conn = sqlite3.connect(db_path)
'txid': txid cursor = conn.cursor()
}
with open(f'cache/{account}_page.json', 'w') as f: # Insert or replace the transaction ID
json.dump(pageCache, f, indent=4) cursor.execute(
'INSERT OR REPLACE INTO tx_pages (account, page_key, txid, timestamp) VALUES (?, ?, ?, ?)',
return pageCache[page]['txid'] (account, page_key, txid, int(time.time()))
)
conn.commit()
conn.close()
return txid
def getTXFromPage(account, page, size=100): def getTXFromPage(account, page, size=100):
if page == 1: if page == 1:
@@ -606,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'
@@ -740,7 +772,6 @@ def getDomain(domain: str):
"message": response['error']['message'] "message": response['error']['message']
} }
} }
return response['result'] return response['result']
def isKnownDomain(domain: str) -> bool: def isKnownDomain(domain: str) -> bool:
@@ -757,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:
@@ -770,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"
@@ -783,6 +814,17 @@ def renewDomain(account, domain):
def getDNS(domain: str): def getDNS(domain: str):
# Get the DNS # Get the DNS
if isSPV():
response = requests.get(f"https://hsd.hns.au/api/v1/nameresource/{domain}")
if response.status_code != 200:
return {
"error": f"Error fetching DNS records: {response.status_code}"
}
response = response.json()
return response.get('records', [])
response = hsd.rpc_getNameResource(domain) response = hsd.rpc_getNameResource(domain)
if response['error'] is not None: if response['error'] is not None:
return { return {
@@ -792,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']:
@@ -804,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"
@@ -876,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
@@ -925,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
@@ -955,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
@@ -966,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']:
@@ -984,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
@@ -999,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
@@ -1023,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"
@@ -1043,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"
@@ -1077,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"
@@ -1109,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"
@@ -1134,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"
@@ -1169,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"
@@ -1194,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"
@@ -1216,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"
@@ -1238,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"
@@ -1275,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"
@@ -1312,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"
@@ -1349,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"
@@ -1398,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"
@@ -1547,7 +1587,7 @@ def zapTXs(account):
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"
@@ -1569,9 +1609,11 @@ def zapTXs(account):
def getxPub(account): def getxPub(account):
account_name = check_account(account) account_name = account
if account.count(":") > 0:
account_name = check_account(account)
if account_name == False: if not account_name:
return { return {
"error": { "error": {
"message": "Invalid account" "message": "Invalid account"
@@ -1587,8 +1629,6 @@ def getxPub(account):
} }
} }
return response['accountKey'] return response['accountKey']
return response
except Exception as e: except Exception as e:
return { return {
"error": { "error": {
@@ -1601,7 +1641,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"
@@ -1641,6 +1681,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
@@ -1651,6 +1692,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
@@ -1697,7 +1739,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
@@ -1741,32 +1783,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
@@ -1775,11 +1824,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"}
@@ -1788,8 +1849,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
@@ -1882,7 +1945,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
@@ -1915,7 +1979,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

View File

@@ -1,45 +0,0 @@
What have you built previously?
- [HNSHosting](https://hnshosting.au)
- [ShakeCities](https://shakecities.com)
- [FireWallet](https://firewallet.au)
- [Git Profile](https://github.com/nathanwoodburn)
Project summary
A Handshake wallet web ui. This will be a HSD wallet web ui that will allow users to manage their Handshake domains via a web interface. This will allow users to easily manage their domains without having to use the command line or bob. One benefit of this is that it will allow users to easily manage their domains from their mobile devices that don't have access to any HNS wallet. This could be done in a secure way by only allowing connections on local network devices (in addition to requiring the wallet credentials).
Features:
- Login with HSD wallet name + password (by default don't show a list of wallets to login to as this could be a security risk)
- View account information in a dashboard
- Available balance
- Total balance
- Pending Transactions
- List of domains
- List of transactions
- Manage domains
- Transfer domains
- Finalize domains
- Edit domains
- Revoke domains (with a warning and requiring the account password)
- Manage wallet
- Send HNS
- Receive HNS
- Auctions
- View bids on domain
- Open auction
- Bid on auction
- Reveal bid
- Redeem bid
- Register domain
Completion requirements:
- Basic functionality including
- View info
- Send/Receive HNS
- Manage domains
After the initial version is completed I will be looking to add more features including the above mentioned features.
The initial version will be completed in 2-3 weeks with a fully fledged version released later as the features are developed and tested.
You can contact me at handshake @ nathan.woodburn.au

126
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,21 +1139,26 @@ 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 = ""
if not os.path.exists(".git"): if not os.path.exists(".git"):
return render_template("settings.html", account=account, return render_template("settings.html", account=account,
hsd_version=account_module.hsdVersion(False), hsd_version=account_module.hsdVersion(False),
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE) error=error,success=success,version="Error",
internal=account_module.HSD_INTERNAL_NODE,
spv=account_module.isSPV())
info = gitinfo.get_git_info() info = gitinfo.get_git_info()
if not info: if not info:
return render_template("settings.html", account=account, return render_template("settings.html", account=account,
hsd_version=account_module.hsdVersion(False), hsd_version=account_module.hsdVersion(False),
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE) error=error,success=success,version="Error",
internal=account_module.HSD_INTERNAL_NODE,
spv=account_module.isSPV())
branch = info['refs'] branch = info['refs']
if branch != "main": if branch != "main":
@@ -1171,7 +1173,8 @@ def settings():
version += ' (New version available)' version += ' (New version available)'
return render_template("settings.html", account=account, return render_template("settings.html", account=account,
hsd_version=account_module.hsdVersion(False), hsd_version=account_module.hsdVersion(False),
error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE) error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE,
spv=account_module.isSPV())
@app.route('/settings/<action>') @app.route('/settings/<action>')
def settings_action(action): def settings_action(action):
@@ -1198,7 +1201,7 @@ def settings_action(action):
if action == "zap": if action == "zap":
resp = account_module.zapTXs(request.cookies.get("account")) resp = account_module.zapTXs(request.cookies.get("account"))
if type(resp) == dict and 'error' in resp: 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")
@@ -1223,7 +1226,7 @@ def settings_action(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")
@@ -1247,7 +1250,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"
@@ -1284,7 +1287,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",
@@ -1323,7 +1326,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)
@@ -1370,7 +1373,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,
@@ -1469,12 +1472,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,
@@ -1500,7 +1503,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)
@@ -1587,7 +1590,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']
@@ -1622,7 +1625,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"
@@ -1708,7 +1711,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
@@ -1722,7 +1725,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:
@@ -1777,9 +1780,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}')
@@ -1795,7 +1798,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"})
@@ -1814,9 +1816,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}')
@@ -1848,7 +1850,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
@@ -1900,3 +1902,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

@@ -68,7 +68,7 @@
<h3 class="mb-1" style="text-align: center;color: rgb(0,255,0);">{{success}}</h3> <h3 class="mb-1" style="text-align: center;color: rgb(0,255,0);">{{success}}</h3>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">Node Settings</h4><small>HSD Version: v{{hsd_version}}</small> <h4 class="card-title">Node Settings</h4><small>HSD Version: v{{hsd_version}}&nbsp; Type: {% if internal %} Internal {% else %} Remote {% endif %} ({% if spv %}SPV{% else %}Full Node{% endif %})</small>
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6><ul class="list-group"> <h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6><ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a> <div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a>