Compare commits
6 Commits
f2cda461ba
...
v2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
1203719eac
|
|||
|
373a71f04d
|
|||
|
b76b873036
|
|||
|
23e714fad8
|
|||
|
a36c69ecfc
|
|||
|
1fd9987bf1
|
@@ -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
|
||||||
@@ -10,9 +10,8 @@ COPY . /app
|
|||||||
|
|
||||||
# Add mount point for data volume
|
# Add mount point for data volume
|
||||||
# VOLUME /data
|
# VOLUME /data
|
||||||
RUN apk add git openssl curl
|
|
||||||
|
|
||||||
ENTRYPOINT ["python3"]
|
ENTRYPOINT ["python3"]
|
||||||
CMD ["server.py"]
|
CMD ["server.py"]
|
||||||
|
|
||||||
FROM builder as dev-envs
|
FROM builder AS dev-envs
|
||||||
|
|||||||
Binary file not shown.
19
README.md
19
README.md
@@ -143,6 +143,25 @@ If you set INTERNAL_HSD=true in the .env file the wallet will start and manage i
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Supported config options are:
|
||||||
|
```yaml
|
||||||
|
spv: true/false
|
||||||
|
prefix: path to hsd data directory
|
||||||
|
flags: list of additional flags to pass to hsd
|
||||||
|
version: version of hsd to use (used when installing HSD from source)
|
||||||
|
chainMigrate: <int> (for users migrating from older versions of HSD)
|
||||||
|
walletMigrate: <int> (for users migrating from older versions of HSD)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support the Project
|
||||||
|
|
||||||
|
If you find FireWallet useful and would like to support its continued development, please consider making a donation. Your contributions help maintain the project and develop new features.
|
||||||
|
|
||||||
|
HNS donations can be sent to: `hs1qh7uzytf2ftwkd9dmjjs7az9qfver5m7dd7x4ej`
|
||||||
|
Other donation options can be found at [my website](https://nathan.woodburn.au/donate)
|
||||||
|
|
||||||
|
Thank you for your support!
|
||||||
|
|
||||||
## Warnings
|
## Warnings
|
||||||
|
|
||||||
- This is a work in progress and is not guaranteed to work
|
- This is a work in progress and is not guaranteed to work
|
||||||
|
|||||||
163
account.py
163
account.py
@@ -64,7 +64,8 @@ HSD_CONFIG = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
CACHE_TTL = int(os.getenv("CACHE_TTL",90))
|
TX_CACHE_TTL = 3600
|
||||||
|
DOMAIN_CACHE_TTL = int(os.getenv("CACHE_TTL",90))
|
||||||
|
|
||||||
if not os.path.exists('hsdconfig.json'):
|
if not os.path.exists('hsdconfig.json'):
|
||||||
with open('hsdconfig.json', 'w') as f:
|
with open('hsdconfig.json', 'w') as f:
|
||||||
@@ -78,8 +79,6 @@ else:
|
|||||||
hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
|
hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
|
||||||
hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT)
|
hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT)
|
||||||
|
|
||||||
cacheTime = 3600
|
|
||||||
|
|
||||||
# Verify the connection
|
# Verify the connection
|
||||||
response = hsd.getInfo()
|
response = hsd.getInfo()
|
||||||
|
|
||||||
@@ -377,7 +376,7 @@ def getBalance(account: str):
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
cache_cutoff = now - (CACHE_TTL * 86400) # Cache TTL in days
|
cache_cutoff = now - (DOMAIN_CACHE_TTL * 86400) # Cache TTL in days
|
||||||
|
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
domain_name = domain['name']
|
domain_name = domain['name']
|
||||||
@@ -488,40 +487,74 @@ def getDomains(account, own=True):
|
|||||||
|
|
||||||
return domains
|
return domains
|
||||||
|
|
||||||
|
def init_tx_page_db():
|
||||||
|
"""Initialize the SQLite database for transaction page cache."""
|
||||||
|
os.makedirs('cache', exist_ok=True)
|
||||||
|
db_path = os.path.join('cache', 'tx_pages.db')
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Create the tx_pages table if it doesn't exist
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS tx_pages (
|
||||||
|
account TEXT,
|
||||||
|
page_key TEXT,
|
||||||
|
txid TEXT,
|
||||||
|
timestamp INTEGER,
|
||||||
|
PRIMARY KEY (account, page_key)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
def getPageTXCache(account, page, size=100):
|
def getPageTXCache(account, page, size=100):
|
||||||
page = f"{page}-{size}"
|
"""Get cached transaction ID from SQLite database."""
|
||||||
if not os.path.exists(f'cache'):
|
account = getxPub(account)
|
||||||
os.mkdir(f'cache')
|
page_key = f"{page}-{size}"
|
||||||
|
|
||||||
if not os.path.exists(f'cache/{account}_page.json'):
|
# Initialize database if needed
|
||||||
with open(f'cache/{account}_page.json', 'w') as f:
|
init_tx_page_db()
|
||||||
f.write('{}')
|
|
||||||
with open(f'cache/{account}_page.json') as f:
|
db_path = os.path.join('cache', 'tx_pages.db')
|
||||||
pageCache = json.load(f)
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
if page in pageCache and pageCache[page]['time'] > int(time.time()) - cacheTime:
|
|
||||||
return pageCache[page]['txid']
|
# Query for the cached transaction ID
|
||||||
|
cursor.execute(
|
||||||
|
'SELECT txid, timestamp FROM tx_pages WHERE account = ? AND page_key = ?',
|
||||||
|
(account, page_key)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if row and row[1] > int(time.time()) - TX_CACHE_TTL:
|
||||||
|
return row[0] # Return the cached txid
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def pushPageTXCache(account, page, txid, size=100):
|
def pushPageTXCache(account, page, txid, size=100):
|
||||||
page = f"{page}-{size}"
|
"""Store transaction ID in SQLite database."""
|
||||||
if not os.path.exists(f'cache/{account}_page.json'):
|
account = getxPub(account)
|
||||||
with open(f'cache/{account}_page.json', 'w') as f:
|
page_key = f"{page}-{size}"
|
||||||
f.write('{}')
|
|
||||||
with open(f'cache/{account}_page.json') as f:
|
# Initialize database if needed
|
||||||
pageCache = json.load(f)
|
init_tx_page_db()
|
||||||
|
|
||||||
pageCache[page] = {
|
db_path = os.path.join('cache', 'tx_pages.db')
|
||||||
'time': int(time.time()),
|
conn = sqlite3.connect(db_path)
|
||||||
'txid': txid
|
cursor = conn.cursor()
|
||||||
}
|
|
||||||
with open(f'cache/{account}_page.json', 'w') as f:
|
# Insert or replace the transaction ID
|
||||||
json.dump(pageCache, f, indent=4)
|
cursor.execute(
|
||||||
|
'INSERT OR REPLACE INTO tx_pages (account, page_key, txid, timestamp) VALUES (?, ?, ?, ?)',
|
||||||
return pageCache[page]['txid']
|
(account, page_key, txid, int(time.time()))
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return txid
|
||||||
|
|
||||||
def getTXFromPage(account, page, size=100):
|
def getTXFromPage(account, page, size=100):
|
||||||
if page == 1:
|
if page == 1:
|
||||||
@@ -740,7 +773,6 @@ def getDomain(domain: str):
|
|||||||
"message": response['error']['message']
|
"message": response['error']['message']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response['result']
|
return response['result']
|
||||||
|
|
||||||
def isKnownDomain(domain: str) -> bool:
|
def isKnownDomain(domain: str) -> bool:
|
||||||
@@ -783,6 +815,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 {
|
||||||
@@ -1569,7 +1612,9 @@ def zapTXs(account):
|
|||||||
|
|
||||||
|
|
||||||
def getxPub(account):
|
def getxPub(account):
|
||||||
account_name = check_account(account)
|
account_name = account
|
||||||
|
if account.count(":") > 0:
|
||||||
|
account_name = check_account(account)
|
||||||
|
|
||||||
if account_name == False:
|
if account_name == False:
|
||||||
return {
|
return {
|
||||||
@@ -1587,8 +1632,6 @@ def getxPub(account):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response['accountKey']
|
return response['accountKey']
|
||||||
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {
|
return {
|
||||||
"error": {
|
"error": {
|
||||||
@@ -1741,25 +1784,34 @@ def checkPreRequisites() -> dict[str, bool]:
|
|||||||
"git": False,
|
"git": False,
|
||||||
"hsd": False
|
"hsd": False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check if node is installed and get version
|
||||||
|
nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True)
|
||||||
|
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 FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Check if node is installed and get version
|
try:
|
||||||
nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True)
|
# Check if npm is installed
|
||||||
if nodeSubprocess.returncode == 0:
|
npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True)
|
||||||
major_version = int(nodeSubprocess.stdout.strip().lstrip('v').split('.')[0])
|
if npmSubprocess.returncode == 0:
|
||||||
if major_version >= HSD_CONFIG.get("minNodeVersion", 20):
|
major_version = int(npmSubprocess.stdout.strip().split('.')[0])
|
||||||
prerequisites["node"] = True
|
if major_version >= HSD_CONFIG.get("minNPMVersion", 8):
|
||||||
|
prerequisites["npm"] = True
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Check if npm is installed
|
try:
|
||||||
npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True)
|
# Check if git is installed
|
||||||
if npmSubprocess.returncode == 0:
|
gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True)
|
||||||
major_version = int(npmSubprocess.stdout.strip().split('.')[0])
|
if gitSubprocess.returncode == 0:
|
||||||
if major_version >= HSD_CONFIG.get("minNPMVersion", 8):
|
prerequisites["git"] = True
|
||||||
prerequisites["npm"] = True
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
# Check if git is installed
|
|
||||||
gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True)
|
|
||||||
if gitSubprocess.returncode == 0:
|
|
||||||
prerequisites["git"] = True
|
|
||||||
|
|
||||||
# Check if hsd is installed
|
# Check if hsd is installed
|
||||||
if os.path.exists("./hsd/bin/hsd"):
|
if os.path.exists("./hsd/bin/hsd"):
|
||||||
@@ -1789,7 +1841,7 @@ def hsdInit():
|
|||||||
for key, value in prerequisites.items():
|
for key, value in prerequisites.items():
|
||||||
if not value:
|
if not value:
|
||||||
print(f" - {key} is missing or does not meet the version requirement.")
|
print(f" - {key} is missing or does not meet the version requirement.")
|
||||||
exit(1)
|
exit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if hsd is installed
|
# Check if hsd is installed
|
||||||
@@ -1915,7 +1967,6 @@ def hsdRestart():
|
|||||||
hsdStart()
|
hsdStart()
|
||||||
|
|
||||||
|
|
||||||
checkPreRequisites()
|
|
||||||
hsdInit()
|
hsdInit()
|
||||||
hsdStart()
|
hsdStart()
|
||||||
# endregion
|
# endregion
|
||||||
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
|
|
||||||
12
main.py
12
main.py
@@ -1148,15 +1148,20 @@ def settings():
|
|||||||
if success == None:
|
if success == None:
|
||||||
success = ""
|
success = ""
|
||||||
|
|
||||||
|
|
||||||
if not os.path.exists(".git"):
|
if not os.path.exists(".git"):
|
||||||
return render_template("settings.html", account=account,
|
return render_template("settings.html", account=account,
|
||||||
hsd_version=account_module.hsdVersion(False),
|
hsd_version=account_module.hsdVersion(False),
|
||||||
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE)
|
error=error,success=success,version="Error",
|
||||||
|
internal=account_module.HSD_INTERNAL_NODE,
|
||||||
|
spv=account_module.isSPV())
|
||||||
info = gitinfo.get_git_info()
|
info = gitinfo.get_git_info()
|
||||||
if not info:
|
if not info:
|
||||||
return render_template("settings.html", account=account,
|
return render_template("settings.html", account=account,
|
||||||
hsd_version=account_module.hsdVersion(False),
|
hsd_version=account_module.hsdVersion(False),
|
||||||
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE)
|
error=error,success=success,version="Error",
|
||||||
|
internal=account_module.HSD_INTERNAL_NODE,
|
||||||
|
spv=account_module.isSPV())
|
||||||
|
|
||||||
branch = info['refs']
|
branch = info['refs']
|
||||||
if branch != "main":
|
if branch != "main":
|
||||||
@@ -1171,7 +1176,8 @@ def settings():
|
|||||||
version += ' (New version available)'
|
version += ' (New version available)'
|
||||||
return render_template("settings.html", account=account,
|
return render_template("settings.html", account=account,
|
||||||
hsd_version=account_module.hsdVersion(False),
|
hsd_version=account_module.hsdVersion(False),
|
||||||
error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE)
|
error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE,
|
||||||
|
spv=account_module.isSPV())
|
||||||
|
|
||||||
@app.route('/settings/<action>')
|
@app.route('/settings/<action>')
|
||||||
def settings_action(action):
|
def settings_action(action):
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<h3 class="mb-1" style="text-align: center;color: rgb(0,255,0);">{{success}}</h3>
|
<h3 class="mb-1" style="text-align: center;color: rgb(0,255,0);">{{success}}</h3>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">Node Settings</h4><small>HSD Version: v{{hsd_version}}</small>
|
<h4 class="card-title">Node Settings</h4><small>HSD Version: v{{hsd_version}} Type: {% if internal %} Internal {% else %} Remote {% endif %} ({% if spv %}SPV{% else %}Full Node{% endif %})</small>
|
||||||
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6><ul class="list-group">
|
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6><ul class="list-group">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a>
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user