Compare commits
35 Commits
feat/WALLE
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
812fc84d3e
|
|||
|
6d318a597b
|
|||
|
83bd6b9643
|
|||
|
c93b2652f5
|
|||
|
86e174c337
|
|||
|
e7b787c30b
|
|||
|
997828795a
|
|||
|
30de2d585e
|
|||
|
56eabfc1fc
|
|||
|
e0f24267f5
|
|||
|
2d51882d20
|
|||
|
06b1eea9ef
|
|||
|
d483cfdcfd
|
|||
|
46ed0173d3
|
|||
|
9dd50d1292
|
|||
|
53148f573e
|
|||
|
e8f052e0d1
|
|||
|
7f450d620a
|
|||
|
41a1bc743f
|
|||
|
30108e3bc5
|
|||
|
a2dc9f43e3
|
|||
|
1203719eac
|
|||
|
373a71f04d
|
|||
|
b76b873036
|
|||
|
23e714fad8
|
|||
|
a36c69ecfc
|
|||
|
1fd9987bf1
|
|||
|
f2cda461ba
|
|||
|
26c5b4a4fa
|
|||
|
7fdc4a3122
|
|||
|
5ff8960b7b
|
|||
|
4c84bc2bbe
|
|||
|
49e378803d
|
|||
|
1c53547047
|
|||
|
080c4402d8
|
32
.dockerignore
Normal file
32
.dockerignore
Normal 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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
40
.gitea/workflows/test.yml
Normal 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
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -16,3 +16,7 @@ customPlugins/
|
|||||||
cache/
|
cache/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
hsd/
|
||||||
|
hsd-data/
|
||||||
|
hsd.lock
|
||||||
|
hsdconfig.json
|
||||||
|
|||||||
21
Dockerfile
21
Dockerfile
@@ -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
57
Dockerfile.hsd
Normal 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.
35
README.md
35
README.md
@@ -124,9 +124,44 @@ SHOW_EXPIRED: Show expired domains (true/false)
|
|||||||
EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary)
|
EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary)
|
||||||
EXPLORER_TX: URL for exploring transactions (default https://shakeshift.com/transaction/)
|
EXPLORER_TX: URL for exploring transactions (default https://shakeshift.com/transaction/)
|
||||||
HSD_NETWORK: Network to connect to (main, regtest, simnet)
|
HSD_NETWORK: Network to connect to (main, regtest, simnet)
|
||||||
|
DISABLE_WALLETDNS: Disable Wallet DNS records when sending HNS to domains (true/false)
|
||||||
|
INTERNAL_HSD: Use internal HSD node (true/false)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Internal HSD
|
||||||
|
|
||||||
|
If you set INTERNAL_HSD=true in the .env file the wallet will start and manage its own HSD node. If you want to override the default HSD config create a file called hsdconfig.json in the same directory as main.py and change the values you want to override. For example to disable SPV and use an existing bob wallet sync (on linux) and set the agent to "SuperCoolDev" you could use the following:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"spv": false,
|
||||||
|
"prefix":"~/.config/Bob/hsd_data",
|
||||||
|
"flags":[
|
||||||
|
"--agent=SuperCoolDev"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
668
account.py
668
account.py
@@ -7,20 +7,24 @@ import re
|
|||||||
import domainLookup
|
import domainLookup
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
|
import atexit
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
HSD_API = os.getenv("HSD_API","")
|
HSD_API = os.getenv("HSD_API","")
|
||||||
HSD_IP = os.getenv("HSD_IP","localhost")
|
HSD_IP = os.getenv("HSD_IP","localhost")
|
||||||
|
|
||||||
HSD_NETWORK = os.getenv("HSD_NETWORK")
|
HSD_NETWORK = os.getenv("HSD_NETWORK", "main")
|
||||||
HSD_WALLET_PORT = 12039
|
HSD_WALLET_PORT = 12039
|
||||||
HSD_NODE_PORT = 12037
|
HSD_NODE_PORT = 12037
|
||||||
|
|
||||||
if not HSD_NETWORK:
|
HSD_NETWORK = HSD_NETWORK.lower()
|
||||||
HSD_NETWORK = "main"
|
|
||||||
else:
|
|
||||||
HSD_NETWORK = HSD_NETWORK.lower()
|
|
||||||
|
|
||||||
if HSD_NETWORK == "simnet":
|
if HSD_NETWORK == "simnet":
|
||||||
HSD_WALLET_PORT = 15039
|
HSD_WALLET_PORT = 15039
|
||||||
@@ -32,16 +36,48 @@ elif HSD_NETWORK == "regtest":
|
|||||||
HSD_WALLET_PORT = 14039
|
HSD_WALLET_PORT = 14039
|
||||||
HSD_NODE_PORT = 14037
|
HSD_NODE_PORT = 14037
|
||||||
|
|
||||||
|
HSD_INTERNAL_NODE = os.getenv("INTERNAL_HSD","false").lower() in ["1","true","yes"]
|
||||||
|
if HSD_INTERNAL_NODE:
|
||||||
|
if HSD_API == "":
|
||||||
|
# Use a random API KEY
|
||||||
|
HSD_API = "firewallet-" + str(int(time.time()))
|
||||||
|
HSD_IP = "localhost"
|
||||||
|
|
||||||
SHOW_EXPIRED = os.getenv("SHOW_EXPIRED")
|
SHOW_EXPIRED = os.getenv("SHOW_EXPIRED")
|
||||||
if SHOW_EXPIRED is None:
|
if SHOW_EXPIRED is None:
|
||||||
SHOW_EXPIRED = False
|
SHOW_EXPIRED = False
|
||||||
|
|
||||||
|
HSD_PROCESS = None
|
||||||
|
SPV_MODE = None
|
||||||
|
|
||||||
|
# Get hsdconfig.json
|
||||||
|
HSD_CONFIG = {
|
||||||
|
"version": "v8.0.0",
|
||||||
|
"chainMigrate": 4,
|
||||||
|
"walletMigrate": 7,
|
||||||
|
"minNodeVersion": 20,
|
||||||
|
"minNpmVersion": 8,
|
||||||
|
"spv": False,
|
||||||
|
"flags": [
|
||||||
|
"--agent=FireWallet"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
TX_CACHE_TTL = 3600
|
||||||
|
DOMAIN_CACHE_TTL = int(os.getenv("CACHE_TTL",90))
|
||||||
|
|
||||||
|
if not os.path.exists('hsdconfig.json'):
|
||||||
|
with open('hsdconfig.json', 'w') as f:
|
||||||
|
f.write(json.dumps(HSD_CONFIG, indent=4))
|
||||||
|
else:
|
||||||
|
with open('hsdconfig.json') as f:
|
||||||
|
hsdConfigTMP = json.load(f)
|
||||||
|
for key in hsdConfigTMP:
|
||||||
|
HSD_CONFIG[key] = hsdConfigTMP[key]
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
@@ -58,6 +94,13 @@ def hsdVersion(format=True):
|
|||||||
info = hsd.getInfo()
|
info = hsd.getInfo()
|
||||||
if 'error' in info:
|
if 'error' in info:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
# Check if SPV mode is enabled
|
||||||
|
global SPV_MODE
|
||||||
|
if info.get('chain',{}).get('options',{}).get('spv',False):
|
||||||
|
SPV_MODE = True
|
||||||
|
else:
|
||||||
|
SPV_MODE = False
|
||||||
if format:
|
if format:
|
||||||
return float('.'.join(info['version'].split(".")[:2]))
|
return float('.'.join(info['version'].split(".")[:2]))
|
||||||
else:
|
else:
|
||||||
@@ -87,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
|
||||||
@@ -185,6 +228,124 @@ def selectWallet(account: str):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def init_domain_db():
|
||||||
|
"""Initialize the SQLite database for domain cache."""
|
||||||
|
os.makedirs('cache', exist_ok=True)
|
||||||
|
db_path = os.path.join('cache', 'domains.db')
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Create the domains table if it doesn't exist
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS domains (
|
||||||
|
name TEXT PRIMARY KEY,
|
||||||
|
info TEXT,
|
||||||
|
last_updated INTEGER
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def getCachedDomains():
|
||||||
|
"""Get cached domain information from SQLite database."""
|
||||||
|
init_domain_db() # Ensure DB exists
|
||||||
|
|
||||||
|
db_path = os.path.join('cache', 'domains.db')
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
conn.row_factory = sqlite3.Row # This allows accessing columns by name
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Get all domains from the database
|
||||||
|
cursor.execute('SELECT name, info, last_updated FROM domains')
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
# Convert to dictionary format
|
||||||
|
domain_cache = {}
|
||||||
|
for row in rows:
|
||||||
|
try:
|
||||||
|
domain_cache[row['name']] = json.loads(row['info'])
|
||||||
|
domain_cache[row['name']]['last_updated'] = row['last_updated']
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error parsing cached data for domain {row['name']}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
return domain_cache
|
||||||
|
|
||||||
|
|
||||||
|
ACTIVE_DOMAIN_UPDATES = set() # Track domains being updated
|
||||||
|
DOMAIN_UPDATE_LOCK = threading.Lock() # For thread-safe access to ACTIVE_DOMAIN_UPDATES
|
||||||
|
|
||||||
|
def update_domain_cache(domain_names: list):
|
||||||
|
"""Fetch domain info and update the SQLite cache."""
|
||||||
|
if not domain_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Filter out domains that are already being updated
|
||||||
|
domains_to_update = []
|
||||||
|
with DOMAIN_UPDATE_LOCK:
|
||||||
|
for domain in domain_names:
|
||||||
|
if domain not in ACTIVE_DOMAIN_UPDATES:
|
||||||
|
ACTIVE_DOMAIN_UPDATES.add(domain)
|
||||||
|
domains_to_update.append(domain)
|
||||||
|
|
||||||
|
if not domains_to_update:
|
||||||
|
# All requested domains are already being updated
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Initialize database
|
||||||
|
init_domain_db()
|
||||||
|
|
||||||
|
db_path = os.path.join('cache', 'domains.db')
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
for domain_name in domains_to_update:
|
||||||
|
try:
|
||||||
|
# Get domain info from node
|
||||||
|
domain_info = getDomain(domain_name)
|
||||||
|
|
||||||
|
if 'error' in domain_info or not domain_info.get('info'):
|
||||||
|
print(f"Failed to get info for domain {domain_name}: {domain_info.get('error', 'Unknown error')}", flush=True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Update or insert into database
|
||||||
|
now = int(time.time())
|
||||||
|
serialized_info = json.dumps(domain_info)
|
||||||
|
|
||||||
|
cursor.execute(
|
||||||
|
'INSERT OR REPLACE INTO domains (name, info, last_updated) VALUES (?, ?, ?)',
|
||||||
|
(domain_name, serialized_info, now)
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Updated cache for domain {domain_name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating cache for domain {domain_name}: {str(e)}")
|
||||||
|
finally:
|
||||||
|
# Always remove from active set, even if there was an error
|
||||||
|
with DOMAIN_UPDATE_LOCK:
|
||||||
|
if domain_name in ACTIVE_DOMAIN_UPDATES:
|
||||||
|
ACTIVE_DOMAIN_UPDATES.remove(domain_name)
|
||||||
|
|
||||||
|
# Commit all changes at once
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating domain cache: {str(e)}", flush=True)
|
||||||
|
# Make sure to clean up the active set on any exception
|
||||||
|
with DOMAIN_UPDATE_LOCK:
|
||||||
|
for domain in domains_to_update:
|
||||||
|
if domain in ACTIVE_DOMAIN_UPDATES:
|
||||||
|
ACTIVE_DOMAIN_UPDATES.remove(domain)
|
||||||
|
|
||||||
|
print("Updated cache for domains")
|
||||||
|
|
||||||
|
|
||||||
def getBalance(account: str):
|
def getBalance(account: str):
|
||||||
# Get the total balance
|
# Get the total balance
|
||||||
info = hsw.getBalance('default', account)
|
info = hsw.getBalance('default', account)
|
||||||
@@ -201,9 +362,66 @@ def getBalance(account: str):
|
|||||||
|
|
||||||
domains = getDomains(account)
|
domains = getDomains(account)
|
||||||
domainValue = 0
|
domainValue = 0
|
||||||
|
domains_to_update = [] # Track domains that need cache updates
|
||||||
|
|
||||||
|
if isSPV():
|
||||||
|
# Initialize database if needed
|
||||||
|
init_domain_db()
|
||||||
|
|
||||||
|
# Connect to the database directly for efficient querying
|
||||||
|
db_path = os.path.join('cache', 'domains.db')
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
now = int(time.time())
|
||||||
|
cache_cutoff = now - (DOMAIN_CACHE_TTL * 86400) # Cache TTL in days
|
||||||
|
|
||||||
|
for domain in domains:
|
||||||
|
domain_name = domain['name']
|
||||||
|
|
||||||
|
# Check if domain is in cache and still fresh
|
||||||
|
cursor.execute(
|
||||||
|
'SELECT info, last_updated FROM domains WHERE name = ?',
|
||||||
|
(domain_name,)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
# Only add domain for update if:
|
||||||
|
# 1. Not in cache or stale
|
||||||
|
# 2. Not currently being updated by another thread
|
||||||
|
with DOMAIN_UPDATE_LOCK:
|
||||||
|
if (not row or row['last_updated'] < cache_cutoff) and domain_name not in ACTIVE_DOMAIN_UPDATES:
|
||||||
|
domains_to_update.append(domain_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Use the cached info
|
||||||
|
try:
|
||||||
|
if row: # Make sure we have data
|
||||||
|
domain_info = json.loads(row['info'])
|
||||||
|
if domain_info.get('info', {}).get('state', "") == "CLOSED":
|
||||||
|
domainValue += domain_info.get('info', {}).get('value', 0)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Only add for update if not already being updated
|
||||||
|
with DOMAIN_UPDATE_LOCK:
|
||||||
|
if domain_name not in ACTIVE_DOMAIN_UPDATES:
|
||||||
|
domains_to_update.append(domain_name)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
else:
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
if domain['state'] == "CLOSED":
|
if domain['state'] == "CLOSED":
|
||||||
domainValue += domain['value']
|
domainValue += domain['value']
|
||||||
|
|
||||||
|
# Start background thread to update cache for missing domains
|
||||||
|
if domains_to_update:
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=update_domain_cache,
|
||||||
|
args=(domains_to_update,),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
total = total - (domainValue/1000000)
|
total = total - (domainValue/1000000)
|
||||||
locked = locked - (domainValue/1000000)
|
locked = locked - (domainValue/1000000)
|
||||||
|
|
||||||
@@ -268,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:
|
|
||||||
pageCache = json.load(f)
|
|
||||||
|
|
||||||
if page in pageCache and pageCache[page]['time'] > int(time.time()) - cacheTime:
|
db_path = os.path.join('cache', 'tx_pages.db')
|
||||||
return pageCache[page]['txid']
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Query for the cached transaction ID
|
||||||
|
cursor.execute(
|
||||||
|
'SELECT txid, timestamp FROM tx_pages WHERE account = ? AND page_key = ?',
|
||||||
|
(account, page_key)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if row and row[1] > int(time.time()) - 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:
|
|
||||||
pageCache = json.load(f)
|
|
||||||
|
|
||||||
pageCache[page] = {
|
# Initialize database if needed
|
||||||
'time': int(time.time()),
|
init_tx_page_db()
|
||||||
'txid': txid
|
|
||||||
}
|
|
||||||
with open(f'cache/{account}_page.json', 'w') as f:
|
|
||||||
json.dump(pageCache, f, indent=4)
|
|
||||||
|
|
||||||
return pageCache[page]['txid']
|
db_path = os.path.join('cache', 'tx_pages.db')
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Insert or replace the transaction ID
|
||||||
|
cursor.execute(
|
||||||
|
'INSERT OR REPLACE INTO tx_pages (account, page_key, txid, timestamp) VALUES (?, ?, ?, ?)',
|
||||||
|
(account, page_key, txid, int(time.time()))
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return txid
|
||||||
|
|
||||||
def getTXFromPage(account, page, size=100):
|
def getTXFromPage(account, page, size=100):
|
||||||
if page == 1:
|
if page == 1:
|
||||||
@@ -386,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'
|
||||||
@@ -408,6 +660,10 @@ def check_hip2(domain: str):
|
|||||||
if not check_address(address, False, True):
|
if not check_address(address, False, True):
|
||||||
return 'Hip2: Lookup succeeded but address is invalid'
|
return 'Hip2: Lookup succeeded but address is invalid'
|
||||||
return address
|
return address
|
||||||
|
|
||||||
|
# Check if DISABLE_WALLETDNS is set
|
||||||
|
if os.getenv("DISABLE_WALLETDNS","").lower() in ["1","true","yes"]:
|
||||||
|
return "No HIP2 record found for this domain"
|
||||||
# Try using WALLET TXT record
|
# Try using WALLET TXT record
|
||||||
address = domainLookup.wallet_txt(domain)
|
address = domainLookup.wallet_txt(domain)
|
||||||
if not address.startswith("hs1"):
|
if not address.startswith("hs1"):
|
||||||
@@ -461,7 +717,12 @@ def send(account, address, amount):
|
|||||||
def isOwnDomain(account, name: str):
|
def isOwnDomain(account, name: str):
|
||||||
# Get domain
|
# Get domain
|
||||||
domain_info = getDomain(name)
|
domain_info = getDomain(name)
|
||||||
owner = getAddressFromCoin(domain_info['info']['owner']['hash'],domain_info['info']['owner']['index'])
|
if 'info' not in domain_info or domain_info['info'] is None:
|
||||||
|
return False
|
||||||
|
if 'owner' not in domain_info['info']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
owner = getAddressFromCoin(domain_info['info']['owner'].get("hash"),domain_info['info']['owner'].get("index"))
|
||||||
# Select the account
|
# Select the account
|
||||||
hsw.rpc_selectWallet(account)
|
hsw.rpc_selectWallet(account)
|
||||||
account = hsw.rpc_getAccount(owner)
|
account = hsw.rpc_getAccount(owner)
|
||||||
@@ -493,6 +754,16 @@ def isOwnPrevout(account, prevout: dict):
|
|||||||
|
|
||||||
|
|
||||||
def getDomain(domain: str):
|
def getDomain(domain: str):
|
||||||
|
if isSPV():
|
||||||
|
response = requests.get(f"https://hsd.hns.au/api/v1/name/{domain}").json()
|
||||||
|
if 'error' in response:
|
||||||
|
return {
|
||||||
|
"error": {
|
||||||
|
"message": response['error']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
|
||||||
# Get the domain
|
# Get the domain
|
||||||
response = hsd.rpc_getNameInfo(domain)
|
response = hsd.rpc_getNameInfo(domain)
|
||||||
if response['error'] is not None:
|
if response['error'] is not None:
|
||||||
@@ -503,11 +774,21 @@ def getDomain(domain: str):
|
|||||||
}
|
}
|
||||||
return response['result']
|
return response['result']
|
||||||
|
|
||||||
|
def isKnownDomain(domain: str) -> bool:
|
||||||
|
# Get the domain
|
||||||
|
response = hsd.rpc_getNameInfo(domain)
|
||||||
|
if response['error'] is not None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if response['result'] is None or response['result'].get('info') is None:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def getAddressFromCoin(coinhash: str, coinindex = 0):
|
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: {response.text}")
|
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:
|
||||||
@@ -520,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"
|
||||||
@@ -533,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 {
|
||||||
@@ -542,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']:
|
||||||
@@ -554,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"
|
||||||
@@ -626,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
|
||||||
@@ -675,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
|
||||||
|
|
||||||
@@ -705,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
|
||||||
|
|
||||||
@@ -716,10 +1008,12 @@ 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']:
|
||||||
|
# Double check the domain is actually in the node
|
||||||
|
if isKnownDomain(domain['name']):
|
||||||
pending.append(bid)
|
pending.append(bid)
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
@@ -732,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
|
||||||
@@ -747,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
|
||||||
|
|
||||||
|
|
||||||
@@ -771,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"
|
||||||
@@ -791,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"
|
||||||
@@ -825,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"
|
||||||
@@ -857,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"
|
||||||
@@ -882,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"
|
||||||
@@ -917,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"
|
||||||
@@ -942,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"
|
||||||
@@ -964,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"
|
||||||
@@ -986,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"
|
||||||
@@ -1023,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"
|
||||||
@@ -1060,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"
|
||||||
@@ -1097,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"
|
||||||
@@ -1146,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"
|
||||||
@@ -1290,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"
|
||||||
@@ -1317,9 +1607,11 @@ def zapTXs(account):
|
|||||||
|
|
||||||
|
|
||||||
def getxPub(account):
|
def getxPub(account):
|
||||||
|
account_name = account
|
||||||
|
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"
|
||||||
@@ -1335,8 +1627,6 @@ def getxPub(account):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response['accountKey']
|
return response['accountKey']
|
||||||
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {
|
return {
|
||||||
"error": {
|
"error": {
|
||||||
@@ -1349,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"
|
||||||
@@ -1389,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
|
||||||
|
|
||||||
|
|
||||||
@@ -1399,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
|
||||||
@@ -1432,12 +1724,21 @@ def generateReport(account, format="{name},{expiry},{value},{maxBid}"):
|
|||||||
|
|
||||||
def convertHNS(value: int):
|
def convertHNS(value: int):
|
||||||
return value/1000000
|
return value/1000000
|
||||||
return value/1000000
|
|
||||||
|
|
||||||
|
SPV_EXTERNAL_ROUTES = [
|
||||||
|
"name",
|
||||||
|
"coin",
|
||||||
|
"tx",
|
||||||
|
"block"
|
||||||
|
]
|
||||||
|
|
||||||
def get_node_api_url(path=''):
|
def get_node_api_url(path=''):
|
||||||
"""Construct a URL for the HSD node API."""
|
"""Construct a URL for the HSD node API."""
|
||||||
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 in SPV mode and the path is one of the external routes, use the external API
|
||||||
|
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
|
||||||
if not path.startswith('/'):
|
if not path.startswith('/'):
|
||||||
@@ -1454,3 +1755,228 @@ def get_wallet_api_url(path=''):
|
|||||||
path = f'/{path}'
|
path = f'/{path}'
|
||||||
return f"{base_url}{path}"
|
return f"{base_url}{path}"
|
||||||
return base_url
|
return base_url
|
||||||
|
|
||||||
|
def isSPV() -> bool:
|
||||||
|
global SPV_MODE
|
||||||
|
if SPV_MODE is None:
|
||||||
|
info = hsd.getInfo()
|
||||||
|
if 'error' in info:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if SPV mode is enabled
|
||||||
|
if info.get('chain',{}).get('options',{}).get('spv',False):
|
||||||
|
SPV_MODE = True
|
||||||
|
else:
|
||||||
|
SPV_MODE = False
|
||||||
|
return SPV_MODE
|
||||||
|
|
||||||
|
# region HSD Internal Node
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def checkPreRequisites() -> dict[str, bool]:
|
||||||
|
prerequisites = {
|
||||||
|
"node": False,
|
||||||
|
"npm": False,
|
||||||
|
"git": 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
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check if git is installed
|
||||||
|
gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True,timeout=2)
|
||||||
|
if gitSubprocess.returncode == 0:
|
||||||
|
prerequisites["git"] = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Check if hsd is installed
|
||||||
|
if os.path.exists("./hsd/bin/hsd"):
|
||||||
|
prerequisites["hsd"] = True
|
||||||
|
|
||||||
|
return prerequisites
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def hsdInit():
|
||||||
|
if not HSD_INTERNAL_NODE:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 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 = {
|
||||||
|
"node": f"Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})",
|
||||||
|
"npm": f"Install npm (version >= {minNPMVersion}) - usually comes with Node.js",
|
||||||
|
"git": "Install Git from https://git-scm.com/downloads"}
|
||||||
|
|
||||||
|
|
||||||
|
# Check if all prerequisites are met (except hsd)
|
||||||
|
if not all(prerequisites[key] for key in prerequisites if key != "hsd"):
|
||||||
|
print("HSD Internal Node prerequisites not met:")
|
||||||
|
for key, value in prerequisites.items():
|
||||||
|
if not value:
|
||||||
|
print(f" - {key} is missing or does not meet the version requirement.",flush=True)
|
||||||
|
if key in PREREQ_MESSAGES:
|
||||||
|
print(PREREQ_MESSAGES[key],flush=True)
|
||||||
|
exit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if hsd is installed
|
||||||
|
if not prerequisites["hsd"]:
|
||||||
|
print("HSD not found, installing...")
|
||||||
|
# If hsd folder exists, remove it
|
||||||
|
if os.path.exists("hsd"):
|
||||||
|
os.rmdir("hsd")
|
||||||
|
|
||||||
|
# Clone hsd repo
|
||||||
|
gitClone = subprocess.run(["git", "clone", "--depth", "1", "--branch", HSD_CONFIG.get("version", "latest"), "https://github.com/handshake-org/hsd.git", "hsd"], capture_output=True, text=True)
|
||||||
|
if gitClone.returncode != 0:
|
||||||
|
print("Failed to clone hsd repository:")
|
||||||
|
print(gitClone.stderr)
|
||||||
|
exit(1)
|
||||||
|
print("Cloned hsd repository.")
|
||||||
|
# Install hsd dependencies
|
||||||
|
print("Installing hsd dependencies...")
|
||||||
|
npmInstall = subprocess.run(["npm", "install"], cwd="hsd", capture_output=True, text=True)
|
||||||
|
if npmInstall.returncode != 0:
|
||||||
|
print("Failed to install hsd dependencies:")
|
||||||
|
print(npmInstall.stderr)
|
||||||
|
exit(1)
|
||||||
|
print("Installed hsd dependencies.")
|
||||||
|
|
||||||
|
def hsdStart():
|
||||||
|
global HSD_PROCESS
|
||||||
|
global SPV_MODE
|
||||||
|
if not HSD_INTERNAL_NODE:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if hsd was started in the last 30 seconds
|
||||||
|
if os.path.exists("hsd.lock"):
|
||||||
|
lock_time = os.path.getmtime("hsd.lock")
|
||||||
|
if time.time() - lock_time < 30:
|
||||||
|
print("HSD was started recently, skipping start.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
os.remove("hsd.lock")
|
||||||
|
|
||||||
|
print("Starting HSD...")
|
||||||
|
# Create a lock file
|
||||||
|
with open("hsd.lock", "w") as f:
|
||||||
|
f.write(str(time.time()))
|
||||||
|
|
||||||
|
# Config lookups with defaults
|
||||||
|
chain_migrate = HSD_CONFIG.get("chainMigrate", False)
|
||||||
|
wallet_migrate = HSD_CONFIG.get("walletMigrate", False)
|
||||||
|
spv = HSD_CONFIG.get("spv", False)
|
||||||
|
prefix = HSD_CONFIG.get("prefix", os.path.join(os.getcwd(), "hsd-data"))
|
||||||
|
|
||||||
|
|
||||||
|
# Base command
|
||||||
|
cmd = [
|
||||||
|
"node",
|
||||||
|
"./hsd/bin/hsd",
|
||||||
|
f"--network={HSD_NETWORK}",
|
||||||
|
f"--prefix={prefix}",
|
||||||
|
f"--api-key={HSD_API}",
|
||||||
|
"--http-host=127.0.0.1",
|
||||||
|
"--log-console=false"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Conditionally add migration flags
|
||||||
|
if chain_migrate:
|
||||||
|
cmd.append(f"--chain-migrate={chain_migrate}")
|
||||||
|
if wallet_migrate:
|
||||||
|
cmd.append(f"--wallet-migrate={wallet_migrate}")
|
||||||
|
SPV_MODE = spv
|
||||||
|
if spv:
|
||||||
|
cmd.append("--spv")
|
||||||
|
|
||||||
|
# Add flags
|
||||||
|
if len(HSD_CONFIG.get("flags",[])) > 0:
|
||||||
|
for flag in HSD_CONFIG.get("flags",[]):
|
||||||
|
cmd.append(flag)
|
||||||
|
|
||||||
|
# Launch process
|
||||||
|
HSD_PROCESS = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
cwd=os.getcwd(),
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"HSD started with PID {HSD_PROCESS.pid}")
|
||||||
|
|
||||||
|
atexit.register(hsdStop)
|
||||||
|
|
||||||
|
# Handle Ctrl+C
|
||||||
|
try:
|
||||||
|
signal.signal(signal.SIGINT, lambda s, f: (hsdStop(), sys.exit(0)))
|
||||||
|
signal.signal(signal.SIGTERM, lambda s, f: (hsdStop(), sys.exit(0)))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to set signal handlers: {str(e)}")
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def hsdStop():
|
||||||
|
global HSD_PROCESS
|
||||||
|
|
||||||
|
if HSD_PROCESS is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Stopping HSD...")
|
||||||
|
|
||||||
|
# Send SIGINT (like Ctrl+C)
|
||||||
|
HSD_PROCESS.send_signal(signal.SIGINT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
HSD_PROCESS.wait(timeout=10) # wait for graceful exit
|
||||||
|
print("HSD shut down cleanly.")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print("HSD did not exit yet, is it alright???")
|
||||||
|
|
||||||
|
# Clean up lock file
|
||||||
|
if os.path.exists("hsd.lock"):
|
||||||
|
os.remove("hsd.lock")
|
||||||
|
|
||||||
|
HSD_PROCESS = None
|
||||||
|
|
||||||
|
def hsdRestart():
|
||||||
|
hsdStop()
|
||||||
|
time.sleep(2)
|
||||||
|
hsdStart()
|
||||||
|
|
||||||
|
|
||||||
|
hsdInit()
|
||||||
|
hsdStart()
|
||||||
|
# endregion
|
||||||
14
docker-compose-internal.yml
Normal file
14
docker-compose-internal.yml
Normal 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
26
docker-compose.yml
Normal 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:
|
||||||
@@ -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
|
||||||
@@ -3,3 +3,5 @@ HSD_IP=localhost
|
|||||||
THEME=black
|
THEME=black
|
||||||
SHOW_EXPIRED=false
|
SHOW_EXPIRED=false
|
||||||
EXPLORER_TX=https://shakeshift.com/transaction/
|
EXPLORER_TX=https://shakeshift.com/transaction/
|
||||||
|
DISABLE_WALLETDNS=false
|
||||||
|
INTERNAL_HSD=false
|
||||||
45
grant.md
45
grant.md
@@ -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
|
|
||||||
172
main.py
172
main.py
@@ -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:
|
||||||
@@ -57,10 +55,6 @@ def blocks_to_time(blocks: int) -> str:
|
|||||||
return f"{days} days"
|
return f"{days} days"
|
||||||
return f"{days} days {hours} hrs"
|
return f"{days} days {hours} hrs"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
# Check if the user is logged in
|
# Check if the user is logged in
|
||||||
@@ -82,6 +76,8 @@ def index():
|
|||||||
return render_template("index.html", account=account, plugins=plugins)
|
return render_template("index.html", account=account, plugins=plugins)
|
||||||
|
|
||||||
info = gitinfo.get_git_info()
|
info = gitinfo.get_git_info()
|
||||||
|
if info is None:
|
||||||
|
return render_template("index.html", account=account, plugins=plugins)
|
||||||
branch = info['refs']
|
branch = info['refs']
|
||||||
commit = info['commit']
|
commit = info['commit']
|
||||||
if commit != latestVersion(branch):
|
if commit != latestVersion(branch):
|
||||||
@@ -116,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:
|
||||||
@@ -198,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}"
|
||||||
|
|
||||||
|
|
||||||
@@ -213,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']:
|
||||||
@@ -284,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 = ""
|
||||||
@@ -298,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:
|
||||||
@@ -367,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'])
|
||||||
@@ -390,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'])
|
||||||
@@ -412,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'])
|
||||||
@@ -431,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'])
|
||||||
@@ -509,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
|
||||||
@@ -562,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 = ""
|
||||||
@@ -609,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'])
|
||||||
|
|
||||||
@@ -628,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'])
|
||||||
|
|
||||||
@@ -647,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"
|
||||||
@@ -678,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'])
|
||||||
|
|
||||||
@@ -724,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)
|
||||||
@@ -737,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:
|
||||||
@@ -751,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")
|
||||||
|
|
||||||
@@ -763,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 = ""
|
||||||
|
|
||||||
|
|
||||||
@@ -822,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}"
|
||||||
@@ -849,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,
|
||||||
@@ -908,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:
|
||||||
@@ -918,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,
|
||||||
@@ -967,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"
|
||||||
|
|
||||||
@@ -999,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')
|
||||||
@@ -1095,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']}")
|
||||||
@@ -1144,22 +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")
|
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")
|
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":
|
||||||
@@ -1174,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)
|
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):
|
||||||
@@ -1191,19 +1191,29 @@ def settings_action(action):
|
|||||||
if 'error' in resp:
|
if 'error' in resp:
|
||||||
return redirect("/settings?error=" + str(resp['error']))
|
return redirect("/settings?error=" + str(resp['error']))
|
||||||
return redirect("/settings?success=Rescan started")
|
return redirect("/settings?success=Rescan started")
|
||||||
elif action == "resend":
|
|
||||||
|
if action == "resend":
|
||||||
resp = account_module.resendTXs()
|
resp = account_module.resendTXs()
|
||||||
if 'error' in resp:
|
if 'error' in resp:
|
||||||
return redirect("/settings?error=" + str(resp['error']))
|
return redirect("/settings?error=" + str(resp['error']))
|
||||||
return redirect("/settings?success=Resent transactions")
|
return redirect("/settings?success=Resent transactions")
|
||||||
|
|
||||||
|
|
||||||
elif 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")
|
||||||
elif action == "xpub":
|
|
||||||
|
if action == "xpub":
|
||||||
xpub = account_module.getxPub(request.cookies.get("account"))
|
xpub = account_module.getxPub(request.cookies.get("account"))
|
||||||
content = "<br><br>"
|
content = "<br><br>"
|
||||||
content += f"<textarea style='display: none;' id='data' rows='4' cols='50'>{xpub}</textarea>"
|
content += f"<textarea style='display: none;' id='data' rows='4' cols='50'>{xpub}</textarea>"
|
||||||
@@ -1214,11 +1224,29 @@ def settings_action(action):
|
|||||||
title="xPub Key",
|
title="xPub Key",
|
||||||
content=f"<code>{xpub}</code>{content}")
|
content=f"<code>{xpub}</code>{content}")
|
||||||
|
|
||||||
|
if action == "restart":
|
||||||
|
resp = account_module.hsdRestart()
|
||||||
|
return render_template("message.html", account=account,
|
||||||
|
title="Restarting",
|
||||||
|
content="The node is restarting. This may take a minute or two. You can close this window.")
|
||||||
|
|
||||||
|
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")
|
||||||
@@ -1242,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"
|
||||||
|
|
||||||
@@ -1261,6 +1289,9 @@ def login():
|
|||||||
wallets = account_module.listWallets()
|
wallets = account_module.listWallets()
|
||||||
wallets = render.wallets(wallets)
|
wallets = render.wallets(wallets)
|
||||||
|
|
||||||
|
# If there are no wallets redirect to either register or import
|
||||||
|
if len(wallets) == 0:
|
||||||
|
return redirect("/welcome")
|
||||||
|
|
||||||
if 'message' in request.args:
|
if 'message' in request.args:
|
||||||
return render_template("login.html",
|
return render_template("login.html",
|
||||||
@@ -1276,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",
|
||||||
@@ -1315,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)
|
||||||
@@ -1362,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,
|
||||||
@@ -1461,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,
|
||||||
@@ -1492,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)
|
||||||
@@ -1579,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']
|
||||||
@@ -1614,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"
|
||||||
|
|
||||||
@@ -1700,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
|
||||||
@@ -1714,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:
|
||||||
@@ -1769,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}')
|
||||||
@@ -1787,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"})
|
||||||
|
|
||||||
@@ -1806,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}')
|
||||||
@@ -1840,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
|
||||||
@@ -1892,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"
|
||||||
12
plugin.py
12
plugin.py
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import account
|
|
||||||
import requests
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Plugin Data
|
# Plugin Data
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|
||||||
|
|||||||
83
render.py
83
render.py
@@ -2,34 +2,9 @@ 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
|
||||||
HSD_API = os.getenv("HSD_API","")
|
|
||||||
HSD_IP = os.getenv("HSD_IP","localhost")
|
|
||||||
|
|
||||||
HSD_NETWORK = os.getenv("HSD_NETWORK")
|
|
||||||
HSD_WALLET_PORT = 12039
|
|
||||||
HSD_NODE_PORT = 12037
|
|
||||||
|
|
||||||
if not HSD_NETWORK:
|
|
||||||
HSD_NETWORK = "main"
|
|
||||||
else:
|
|
||||||
HSD_NETWORK = HSD_NETWORK.lower()
|
|
||||||
|
|
||||||
if HSD_NETWORK == "simnet":
|
|
||||||
HSD_WALLET_PORT = 15039
|
|
||||||
HSD_NODE_PORT = 15037
|
|
||||||
elif HSD_NETWORK == "testnet":
|
|
||||||
HSD_WALLET_PORT = 13039
|
|
||||||
HSD_NODE_PORT = 13037
|
|
||||||
elif HSD_NETWORK == "regtest":
|
|
||||||
HSD_WALLET_PORT = 14039
|
|
||||||
HSD_NODE_PORT = 14037
|
|
||||||
|
|
||||||
hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
|
|
||||||
|
|
||||||
# Get Explorer URL
|
# Get Explorer URL
|
||||||
TX_EXPLORER_URL = os.getenv("EXPLORER_TX")
|
TX_EXPLORER_URL = os.getenv("EXPLORER_TX")
|
||||||
@@ -78,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'
|
||||||
|
|
||||||
@@ -205,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>"
|
||||||
@@ -276,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']
|
||||||
@@ -284,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
|
||||||
|
|
||||||
@@ -339,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>"
|
||||||
@@ -433,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":
|
||||||
@@ -472,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
|
||||||
@@ -491,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":
|
||||||
@@ -510,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):
|
||||||
@@ -521,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
|
||||||
@@ -540,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:
|
||||||
@@ -558,8 +533,10 @@ def renderDomainAsync(namehash: str) -> None:
|
|||||||
|
|
||||||
if namehash in cache:
|
if namehash in cache:
|
||||||
return
|
return
|
||||||
# Fetch the name outside the lock (network call)
|
# Fetch the name outside the lock (network call) using hsd.hns.au
|
||||||
name = hsd.rpc_getNameByHash(namehash)
|
# name = account.hsd.rpc_getNameByHash(namehash)
|
||||||
|
name = requests.get(f"https://hsd.hns.au/api/v1/namehash/{namehash}").json()
|
||||||
|
|
||||||
if name["error"] is None:
|
if name["error"] is None:
|
||||||
name = name["result"]
|
name = name["result"]
|
||||||
rendered = renderDomain(name)
|
rendered = renderDomain(name)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -68,9 +68,8 @@
|
|||||||
<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}} 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>
|
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6><ul class="list-group">
|
||||||
<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>
|
||||||
<h3>Rescan</h3><span>Rescan the blockchain for transactions</span>
|
<h3>Rescan</h3><span>Rescan the blockchain for transactions</span>
|
||||||
@@ -78,7 +77,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/resend">Resend</a>
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/resend">Resend</a>
|
||||||
<h3>Resend unconfirmed transactions</h3><span>Resend any transactions that haven't been mined yet.</span>
|
<h3>Resend unconfirmed transactions</h3><span>Resend any transactions that haven't been mined yet.</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
@@ -86,7 +85,20 @@
|
|||||||
<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>
|
||||||
</ul>
|
<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's API</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% if internal %}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/restart">Restart Node</a>
|
||||||
|
<h3>Restart Internal Node</h3><span>This will attempt to restart the HSD node</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
47
templates/welcome.html
Normal file
47
templates/welcome.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="dark" lang="en-au" style="height: 100%;">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>Welcome to FireWallet</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i&display=swap">
|
||||||
|
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="d-flex align-items-center bg-gradient-primary" style="height: 100%;">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-9 col-lg-12 col-xl-10">
|
||||||
|
<h1 class="text-center" style="color: var(--bs-danger);background: var(--bs-primary);">{{error}}</h1>
|
||||||
|
<div class="card shadow-lg my-5 o-hidden border-0" style="padding-top: 50px;padding-bottom: 50px;">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 d-none d-lg-flex">
|
||||||
|
<div class="flex-grow-1 bg-login-image" style="background: url("/assets/img/favicon.png") center / contain no-repeat;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="text-center p-5">
|
||||||
|
<div class="text-center">
|
||||||
|
<h4 class="mb-4">Welcome to FireWallet!</h4>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group-vertical btn-group-lg gap-1" role="group"><a class="btn btn-primary" role="button" href="/register">Create a new wallet</a><a class="btn btn-primary" role="button" href="/import-wallet">Import an existing wallet</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="/assets/js/script.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user