Compare commits
49 Commits
feat/aucti
...
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
|
|||
|
792688064e
|
|||
|
599c0df00c
|
|||
|
a619d78efd
|
|||
|
f090b7b71a
|
|||
|
545a0b9475
|
|||
|
501091eeae
|
|||
|
6911e3663c
|
|||
|
eda690544d
|
|||
|
e67c178ea7
|
|||
|
631d558377
|
|||
|
1d5ed059b3
|
|||
|
747ac575fa
|
|||
|
e574933302
|
|||
|
c0f0dc5010
|
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
|
||||||
@@ -17,7 +28,7 @@ jobs:
|
|||||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||||
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install docker-ce-cli -y
|
apt-get install docker-ce-cli -y
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
run : |
|
run : |
|
||||||
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
|
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
|
||||||
@@ -34,8 +45,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
docker build -t firewallet:$tag_num .
|
docker build --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VCS_REF=$GITEA_SHA --build-arg VERSION=$GITEA_TAG --file ${{matrix.variant.dockerfile}} -t firewallet${{matrix.variant.tag_suffix}}:$tag_num .
|
||||||
docker tag firewallet:$tag_num git.woodburn.au/nathanwoodburn/firewallet:$tag_num
|
docker tag firewallet${{matrix.variant.tag_suffix}}:$tag_num git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag_num
|
||||||
docker push git.woodburn.au/nathanwoodburn/firewallet:$tag_num
|
docker push git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag_num
|
||||||
docker tag firewallet:$tag_num git.woodburn.au/nathanwoodburn/firewallet:$tag
|
docker tag firewallet${{matrix.variant.tag_suffix}}:$tag_num git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag
|
||||||
docker push git.woodburn.au/nathanwoodburn/firewallet:$tag
|
docker push git.woodburn.au/nathanwoodburn/firewallet${{matrix.variant.tag_suffix}}:$tag
|
||||||
40
.gitea/workflows/test.yml
Normal file
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
|
||||||
|
|||||||
1168
account.py
1168
account.py
File diff suppressed because it is too large
Load Diff
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:
|
||||||
@@ -6,10 +6,13 @@ import subprocess
|
|||||||
import binascii
|
import binascii
|
||||||
import datetime
|
import datetime
|
||||||
import dns.asyncresolver
|
import dns.asyncresolver
|
||||||
|
import dns.message
|
||||||
|
import dns.query
|
||||||
|
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
|
||||||
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Disable insecure request warnings (since we are manually verifying the certificate)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Disable insecure request warnings (since we are manually verifying the certificate)
|
||||||
|
|
||||||
@@ -56,7 +59,7 @@ def hip2(domain: str):
|
|||||||
|
|
||||||
domains = []
|
domains = []
|
||||||
for ext in cert_obj.extensions:
|
for ext in cert_obj.extensions:
|
||||||
if ext.oid == x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
if ext.oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
||||||
san_list = ext.value.get_values_for_type(x509.DNSName)
|
san_list = ext.value.get_values_for_type(x509.DNSName)
|
||||||
domains.extend(san_list)
|
domains.extend(san_list)
|
||||||
|
|
||||||
@@ -120,13 +123,39 @@ def hip2(domain: str):
|
|||||||
print(f"Hip2: Lookup failed with error: {e}",flush=True)
|
print(f"Hip2: Lookup failed with error: {e}",flush=True)
|
||||||
return "Hip2: Lookup failed."
|
return "Hip2: Lookup failed."
|
||||||
|
|
||||||
|
def wallet_txt(domain: str, doh_url="https://hnsdoh.com/dns-query"):
|
||||||
|
with httpx.Client() as client:
|
||||||
|
q = dns.message.make_query(domain, dns.rdatatype.from_text("TYPE262"))
|
||||||
|
r = dns.query.https(q, doh_url, session=client)
|
||||||
|
|
||||||
|
if not r.answer:
|
||||||
|
return "No wallet address found for this domain"
|
||||||
|
|
||||||
|
wallet_record = "No WALLET record found"
|
||||||
|
for ans in r.answer:
|
||||||
|
raw = ans[0].to_wire() # type: ignore
|
||||||
|
try:
|
||||||
|
data = raw[1:].decode("utf-8", errors="ignore")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return f"Unknown WALLET record format: {raw.hex()}"
|
||||||
|
|
||||||
|
if data.startswith("HNS:"):
|
||||||
|
wallet_record = data[4:]
|
||||||
|
break
|
||||||
|
elif data.startswith("HNS "):
|
||||||
|
wallet_record = data[4:]
|
||||||
|
break
|
||||||
|
elif data.startswith('"HNS" '):
|
||||||
|
wallet_record = data[6:].strip('"')
|
||||||
|
break
|
||||||
|
return wallet_record
|
||||||
|
|
||||||
def resolve_with_doh(query_name, doh_url="https://hnsdoh.com/dns-query"):
|
def resolve_with_doh(query_name, doh_url="https://hnsdoh.com/dns-query"):
|
||||||
with httpx.Client() as client:
|
with httpx.Client() as client:
|
||||||
q = dns.message.make_query(query_name, dns.rdatatype.A)
|
q = dns.message.make_query(query_name, dns.rdatatype.A)
|
||||||
r = dns.query.https(q, doh_url, session=client)
|
r = dns.query.https(q, doh_url, session=client)
|
||||||
|
|
||||||
ip = r.answer[0][0].address
|
ip = r.answer[0][0].address # type: ignore
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
def resolve_TLSA_with_doh(query_name, doh_url="https://hnsdoh.com/dns-query"):
|
def resolve_TLSA_with_doh(query_name, doh_url="https://hnsdoh.com/dns-query"):
|
||||||
@@ -142,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
|
||||||
@@ -2,4 +2,6 @@ HSD_API=123480615465636893475aCwyaae6s45
|
|||||||
HSD_IP=localhost
|
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
|
|
||||||
29
plugin.py
29
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)
|
||||||
@@ -148,11 +151,14 @@ def getPluginData(pluginStr: str):
|
|||||||
|
|
||||||
|
|
||||||
def getPluginFunctions(plugin: str):
|
def getPluginFunctions(plugin: str):
|
||||||
plugin = import_module(plugin.replace("/","."))
|
imported_plugin = import_module(plugin.replace("/","."))
|
||||||
return plugin.functions
|
return imported_plugin.functions
|
||||||
|
|
||||||
|
|
||||||
def runPluginFunction(plugin: str, function: str, params: dict, authentication: str):
|
def runPluginFunction(plugin: str, function: str, params: dict, authentication: (str|None)):
|
||||||
|
if not authentication:
|
||||||
|
return {"error": "Authentication required"}
|
||||||
|
|
||||||
plugin_module = import_module(plugin.replace("/","."))
|
plugin_module = import_module(plugin.replace("/","."))
|
||||||
if function not in plugin_module.functions:
|
if function not in plugin_module.functions:
|
||||||
return {"error": "Function not found"}
|
return {"error": "Function not found"}
|
||||||
@@ -168,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)
|
||||||
@@ -189,13 +196,13 @@ def runPluginFunction(plugin: str, function: str, params: dict, authentication:
|
|||||||
|
|
||||||
|
|
||||||
def getPluginFunctionInputs(plugin: str, function: str):
|
def getPluginFunctionInputs(plugin: str, function: str):
|
||||||
plugin = import_module(plugin.replace("/","."))
|
imported_plugin = import_module(plugin.replace("/","."))
|
||||||
return plugin.functions[function]["params"]
|
return imported_plugin.functions[function]["params"]
|
||||||
|
|
||||||
|
|
||||||
def getPluginFunctionReturns(plugin: str, function: str):
|
def getPluginFunctionReturns(plugin: str, function: str):
|
||||||
plugin = import_module(plugin.replace("/","."))
|
imported_plugin = import_module(plugin.replace("/","."))
|
||||||
return plugin.functions[function]["returns"]
|
return imported_plugin.functions[function]["returns"]
|
||||||
|
|
||||||
|
|
||||||
def getDomainFunctions():
|
def getDomainFunctions():
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|
||||||
|
|||||||
126
render.py
126
render.py
@@ -2,36 +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")
|
|
||||||
if HSD_IP is None:
|
|
||||||
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")
|
||||||
@@ -40,6 +13,24 @@ if TX_EXPLORER_URL is None:
|
|||||||
|
|
||||||
|
|
||||||
NAMEHASH_CACHE = 'user_data/namehash_cache.json'
|
NAMEHASH_CACHE = 'user_data/namehash_cache.json'
|
||||||
|
# Validate cache version
|
||||||
|
if os.path.exists(NAMEHASH_CACHE):
|
||||||
|
with open(NAMEHASH_CACHE, 'r') as f:
|
||||||
|
cache = json.load(f)
|
||||||
|
if not isinstance(cache, dict):
|
||||||
|
print("Invalid namehash cache format. Resetting cache.")
|
||||||
|
with open(NAMEHASH_CACHE, 'w') as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
# Check if cache entries are valid
|
||||||
|
for key in cache:
|
||||||
|
if not cache[key].startswith("<a href='/manage/"):
|
||||||
|
print(f"Invalid cache entry for {key}. Resetting cache.")
|
||||||
|
with open(NAMEHASH_CACHE, 'w') as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CACHE_LOCK = threading.Lock()
|
CACHE_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
@@ -62,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'
|
||||||
|
|
||||||
@@ -78,6 +69,7 @@ actionMap = {
|
|||||||
"UPDATE": "Updated ",
|
"UPDATE": "Updated ",
|
||||||
"REGISTER": "Registered ",
|
"REGISTER": "Registered ",
|
||||||
"RENEW": "Renewed ",
|
"RENEW": "Renewed ",
|
||||||
|
"OPEN": "Opened ",
|
||||||
"BID": "Bid on ",
|
"BID": "Bid on ",
|
||||||
"REVEAL": "Revealed bid for ",
|
"REVEAL": "Revealed bid for ",
|
||||||
"REDEEM": "Redeemed bid for ",
|
"REDEEM": "Redeemed bid for ",
|
||||||
@@ -89,6 +81,7 @@ actionMapPlural = {
|
|||||||
"UPDATE": "Updated multiple domains' records",
|
"UPDATE": "Updated multiple domains' records",
|
||||||
"REGISTER": "Registered multiple domains",
|
"REGISTER": "Registered multiple domains",
|
||||||
"RENEW": "Renewed multiple domains",
|
"RENEW": "Renewed multiple domains",
|
||||||
|
"OPEN": "Opened multiple domains",
|
||||||
"BID": "Bid on multiple domains",
|
"BID": "Bid on multiple domains",
|
||||||
"REVEAL": "Revealed multiple bids",
|
"REVEAL": "Revealed multiple bids",
|
||||||
"REDEEM": "Redeemed multiple bids",
|
"REDEEM": "Redeemed multiple bids",
|
||||||
@@ -187,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>"
|
||||||
@@ -258,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']
|
||||||
@@ -266,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
|
||||||
|
|
||||||
@@ -302,7 +295,6 @@ def bids(bids,reveals):
|
|||||||
'value': value,
|
'value': value,
|
||||||
'sort_value': value if revealed else lockup # Use value for sorting if revealed, otherwise lockup
|
'sort_value': value if revealed else lockup # Use value for sorting if revealed, otherwise lockup
|
||||||
})
|
})
|
||||||
|
|
||||||
# Sort by the sort_value in descending order (highest first)
|
# Sort by the sort_value in descending order (highest first)
|
||||||
bid_data.sort(key=lambda x: x['sort_value'], reverse=True)
|
bid_data.sort(key=lambda x: x['sort_value'], reverse=True)
|
||||||
|
|
||||||
@@ -322,22 +314,23 @@ 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)));' 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>"
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
def bidDomains(bids,domains, sortbyDomains=False, outbids=[]):
|
def bidDomains(bids,domains, sortbyDomains=False):
|
||||||
html = ''
|
html = ''
|
||||||
|
|
||||||
if not sortbyDomains:
|
if not sortbyDomains:
|
||||||
for bid in bids:
|
for bid in bids:
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
@@ -351,15 +344,13 @@ def bidDomains(bids,domains, sortbyDomains=False, outbids=[]):
|
|||||||
bidDisplay = f'<b>{bidValue:,.2f}</b> (+{blind:,.2f}) HNS'
|
bidDisplay = f'<b>{bidValue:,.2f}</b> (+{blind:,.2f}) HNS'
|
||||||
else:
|
else:
|
||||||
bidDisplay = f'<b>{bidValue:,.2f}</b> HNS'
|
bidDisplay = f'<b>{bidValue:,.2f}</b> HNS'
|
||||||
|
|
||||||
|
|
||||||
html += "<tr>"
|
html += "<tr>"
|
||||||
if domain['name'] in outbids:
|
html += f"<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' href='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
||||||
html += f"<td style='background-color: red;'><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' href='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
|
||||||
else:
|
|
||||||
html += f"<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' href='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
|
||||||
html += f"<td>{domain['state']}</td>"
|
html += f"<td>{domain['state']}</td>"
|
||||||
html += f"<td style='white-space: nowrap;'>{bidDisplay}</td>"
|
html += f"<td style='white-space: nowrap;'>{bidDisplay}</td>"
|
||||||
html += f"<td class='hide-mobile'>{bid['height']:,}</td>"
|
html += f"<td class='hide-mobile'>{domain['height']:,}</td>"
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
else:
|
else:
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
@@ -375,7 +366,7 @@ def bidDomains(bids,domains, sortbyDomains=False, outbids=[]):
|
|||||||
html += f"<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' href='/auction/{domain['name']}'>{renderDomain(domain['name'])}</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)));' href='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
||||||
html += f"<td>{domain['state']}</td>"
|
html += f"<td>{domain['state']}</td>"
|
||||||
html += f"<td>{bidDisplay}</td>"
|
html += f"<td>{bidDisplay}</td>"
|
||||||
html += f"<td class='hide-mobile'>{bid['height']:,}</td>"
|
html += f"<td class='hide-mobile'>{domain['height']:,}</td>"
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
return html
|
return html
|
||||||
|
|
||||||
@@ -417,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":
|
||||||
@@ -456,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
|
||||||
@@ -475,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":
|
||||||
@@ -494,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):
|
||||||
@@ -505,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
|
||||||
@@ -524,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:
|
||||||
@@ -542,12 +533,15 @@ def renderDomainAsync(namehash: str) -> None:
|
|||||||
|
|
||||||
if namehash in cache:
|
if namehash in cache:
|
||||||
return
|
return
|
||||||
|
# Fetch the name outside the lock (network call) using hsd.hns.au
|
||||||
|
# name = account.hsd.rpc_getNameByHash(namehash)
|
||||||
|
name = requests.get(f"https://hsd.hns.au/api/v1/namehash/{namehash}").json()
|
||||||
|
|
||||||
# Fetch the name outside the lock (network call)
|
|
||||||
name = hsd.rpc_getNameByHash(namehash)
|
|
||||||
if name["error"] is None:
|
if name["error"] is None:
|
||||||
name = name["result"]
|
name = name["result"]
|
||||||
rendered = renderDomain(name)
|
rendered = renderDomain(name)
|
||||||
|
rendered = f"<a href='/manage/{name}' target='_blank' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));'>{rendered}</a>"
|
||||||
|
|
||||||
|
|
||||||
with CACHE_LOCK:
|
with CACHE_LOCK:
|
||||||
with open(NAMEHASH_CACHE, 'r') as f:
|
with open(NAMEHASH_CACHE, 'r') as f:
|
||||||
@@ -556,7 +550,7 @@ def renderDomainAsync(namehash: str) -> None:
|
|||||||
with open(NAMEHASH_CACHE, 'w') as f:
|
with open(NAMEHASH_CACHE, 'w') as f:
|
||||||
json.dump(cache, f)
|
json.dump(cache, f)
|
||||||
|
|
||||||
return rendered
|
return
|
||||||
else:
|
else:
|
||||||
print(f"Error fetching name for hash {namehash}: {name['error']}", flush=True)
|
print(f"Error fetching name for hash {namehash}: {name['error']}", flush=True)
|
||||||
|
|
||||||
|
|||||||
11
server.py
11
server.py
@@ -1,4 +1,3 @@
|
|||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
from main import app
|
from main import app
|
||||||
@@ -17,8 +16,8 @@ def gunicornServer():
|
|||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
for key, value in self.options.items():
|
for key, value in self.options.items():
|
||||||
if key in self.cfg.settings and value is not None:
|
if key in self.cfg.settings and value is not None: # type: ignore
|
||||||
self.cfg.set(key.lower(), value)
|
self.cfg.set(key.lower(), value) # type: ignore
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
return self.application
|
return self.application
|
||||||
@@ -32,13 +31,13 @@ def gunicornServer():
|
|||||||
gunicorn_app.run()
|
gunicorn_app.run()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Check if --gunicorn is in the command line arguments
|
# Check if --gunicorn is in the command line arguments
|
||||||
if "--gunicorn" in sys.argv:
|
if "--gunicorn" in sys.argv:
|
||||||
gunicornServer()
|
gunicornServer()
|
||||||
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)
|
||||||
|
|||||||
2
templates/assets/js/dashboard.min.js
vendored
2
templates/assets/js/dashboard.min.js
vendored
@@ -1 +1 @@
|
|||||||
function createCard(e,n,t){if(document.getElementById(t)&&document.getElementById(t).remove(),n<=0)return;const s=document.createElement("div");s.classList.add("col-md-6","col-xl-3","mb-4"),s.id=t,html=`\n <div class="card shadow border-start-warning py-2">\n <div class="card-body">\n <div class="row align-items-center no-gutters">\n <div class="col me-2">\n <div class="text-uppercase text-warning fw-bold text-xs mb-1"><span>${e}</span></div>\n <div class="text-dark fw-bold h5 mb-0"><span id="${e}">${n}</span></div>\n </div>\n <div class="col"><a class="btn btn-primary" role="button" href="/all/${t.toLowerCase()}">${t} All</a></div>\n <div class="col-auto"><svg class="fa-2x text-gray-300" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">\n <g>\n <rect fill="none" height="24" width="24"></rect>\n </g>\n <g>\n <path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M7,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C8.5,12.83,7.83,13.5,7,13.5z M12,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C13.5,12.83,12.83,13.5,12,13.5z M17,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C18.5,12.83,17.83,13.5,17,13.5z"></path>\n </g>\n </svg></div>\n </div>\n </div>`,s.innerHTML=html,document.getElementById("actions-row").appendChild(s)}async function updateActions(){const e={Finalize:"Pending Finalizes",Register:"Pending Register",Redeem:"Pending Redeem",Reveal:"Pending Reveal"},n=Object.keys(e).map((e=>request(`wallet/pending${e}`).then((n=>({id:e,result:n}))))),t=await Promise.all(n);for(const{id:n,result:s}of t)"Error"!==s&&createCard(e[n],s.length,n);const s=await request("wallet/possibleOutbids");if("Error"===s)return;const d=document.getElementById("outbids");if(d&&d.remove(),s.length<=0)return;const i=document.createElement("div");i.classList.add("col-md-6","col-xl-3","mb-4"),i.id="outbids",i.innerHTML=`\n <div class="card shadow border-start-warning py-2">\n <div class="card-body">\n <div class="row align-items-center no-gutters">\n <div class="col me-2">\n <div class="text-uppercase text-warning fw-bold text-xs mb-1"><span>Names with possible outbids</span></div>\n <div class="text-dark fw-bold h5 mb-0"><span id="outbids-count">${s.length}</span></div>\n </div>\n <div class="col"><a class="btn btn-primary" role="button" href="/auctions?outbids=true">Show All</a></div>\n <div class="col-auto">\n <svg class="fa-2x text-gray-300" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">\n <g><rect fill="none" height="24" width="24"></rect></g>\n <g>\n <path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M7,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C8.5,12.83,7.83,13.5,7,13.5z M12,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C13.5,12.83,12.83,13.5,12,13.5z M17,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C18.5,12.83,17.83,13.5,17,13.5z"></path>\n </g>\n </svg>\n </div>\n </div>\n </div>\n </div>\n `,document.getElementById("actions-row").appendChild(i)}window.addEventListener("load",(async()=>{updateActions()})),setInterval((async function(){updateActions()}),2e4);
|
function createCard(e,n,t){if(document.getElementById(t)&&document.getElementById(t).remove(),n<=0)return;const a=document.createElement("div");a.classList.add("col-md-6","col-xl-3","mb-4"),a.id=t,html=`\n <div class="card shadow border-start-warning py-2">\n <div class="card-body">\n <div class="row align-items-center no-gutters">\n <div class="col me-2">\n <div class="text-uppercase text-warning fw-bold text-xs mb-1"><span>${e}</span></div>\n <div class="text-dark fw-bold h5 mb-0"><span id="${e}">${n}</span></div>\n </div>\n <div class="col"><a class="btn btn-primary" role="button" href="/all/${t.toLowerCase()}">${t} All</a></div>\n <div class="col-auto"><svg class="fa-2x text-gray-300" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">\n <g>\n <rect fill="none" height="24" width="24"></rect>\n </g>\n <g>\n <path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M7,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C8.5,12.83,7.83,13.5,7,13.5z M12,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C13.5,12.83,12.83,13.5,12,13.5z M17,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C18.5,12.83,17.83,13.5,17,13.5z"></path>\n </g>\n </svg></div>\n </div>\n </div>`,a.innerHTML=html,document.getElementById("actions-row").appendChild(a)}async function updateActions(){const e={Finalize:"Pending Finalizes",Register:"Pending Register",Redeem:"Pending Redeem",Reveal:"Pending Reveal"};for(const n in e){const t=await request(`wallet/pending${n}`);"Error"!=t&&createCard(e[n],t.length,n)}}window.addEventListener("load",(async()=>{updateActions()})),setInterval((async function(){updateActions()}),2e4);
|
||||||
@@ -66,9 +66,9 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="stick-right">{{next_action|safe}}</div>
|
<div id="next-action" class="stick-right">{{next_action|safe}}</div>
|
||||||
<h4 class="card-title">{{rendered}}</h4>
|
<h4 class="card-title">{{rendered}}</h4>
|
||||||
<h6 class="text-muted mb-2 card-subtitle">{{next | safe}}</h6>
|
<h6 class="text-muted mb-2 card-subtitle" id="next">{{next | safe}}</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,11 +96,89 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="bids-tbody">
|
||||||
{{bids | safe}}
|
<tr id="loading-row">
|
||||||
|
<td colspan="5" class="text-center">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
Loading bids...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function loadBids(initial = false) {
|
||||||
|
const tbody = document.getElementById('bids-tbody');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch all required data
|
||||||
|
const response = await fetch(`/api/v1/wallet/domainBids?domain={{search_term}}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (initial) {
|
||||||
|
if (response.ok && data.result) {
|
||||||
|
tbody.innerHTML = data.result;
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No bids found. <a href="/auction/{{search_term}}/scan">Rescan Auction</a></td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mempoolResponse = await fetch('/api/v1/hsd/mempoolBids');
|
||||||
|
const nextStateResponse = await fetch(`/api/v1/hsd/nextAuctionState?domain={{search_term}}`);
|
||||||
|
|
||||||
|
if (!initial) {
|
||||||
|
if (response.ok && data.result) {
|
||||||
|
tbody.innerHTML = data.result;
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No bids found. <a href="/auction/{{search_term}}/scan">Rescan Auction</a></td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const nextStateData = await nextStateResponse.json();
|
||||||
|
|
||||||
|
if (nextStateResponse.ok && nextStateData.state) {
|
||||||
|
document.getElementById('next').innerHTML = nextStateData.next;
|
||||||
|
document.getElementById('next-action').innerHTML = nextStateData.next_action;
|
||||||
|
} else {
|
||||||
|
document.getElementById('next').innerHTML = 'Unknown';
|
||||||
|
document.getElementById('next-action').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mempoolData = await mempoolResponse.json();
|
||||||
|
if (mempoolResponse.ok && mempoolData.result) {
|
||||||
|
const domainBids = mempoolData.result['{{search_term}}'];
|
||||||
|
if (domainBids && domainBids.length > 0) {
|
||||||
|
let mempoolRows = '';
|
||||||
|
domainBids.forEach(bid => {
|
||||||
|
const bidValue = bid.revealed ? `${(bid.value / 1000000).toFixed(2)} HNS` : 'Hidden until reveal';
|
||||||
|
const lockupValue = (bid.lockup / 1000000).toFixed(2);
|
||||||
|
const blindValue = bid.revealed ? `${((bid.lockup - bid.value) / 1000000).toFixed(2)} HNS` : 'Hidden until reveal';
|
||||||
|
const type = bid.revealed ? 'Reveal' : 'Bid';
|
||||||
|
mempoolRows += `<tr class="table-warning">
|
||||||
|
<td>${lockupValue} HNS</td>
|
||||||
|
<td>${bidValue}</td>
|
||||||
|
<td>${blindValue}</td>
|
||||||
|
<td>${bid.owner}</td>
|
||||||
|
<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='https://shakeshift.com/transaction/${bid.txid}'>Mempool ${type} 🔗</a></td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
tbody.innerHTML += mempoolRows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading bids:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load bids when page loads
|
||||||
|
document.addEventListener('DOMContentLoaded', () => loadBids(true));
|
||||||
|
|
||||||
|
// Auto-refresh bids every 20 seconds
|
||||||
|
setInterval(() => loadBids(false), 20000);
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,25 +68,37 @@
|
|||||||
<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>
|
</div>
|
||||||
</div>
|
</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">
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/zap">Zap</a>
|
||||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/zap">Zap</a>
|
<h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
|
||||||
<h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
<li class="list-group-item">
|
||||||
</ul>
|
<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