6 Commits

8 changed files with 288 additions and 85 deletions

Binary file not shown.

View File

@@ -35,13 +35,13 @@ Also available as a docker image:
To run using a HSD running directly on the host: To run using a HSD running directly on the host:
```bash ```bash
sudo docker run --network=host -e hsd_api=yourapikeyhere git.woodburn.au/nathanwoodburn/firewallet:latest sudo docker run --network=host -e HSD_API=yourapikeyhere git.woodburn.au/nathanwoodburn/firewallet:latest
``` ```
If you have HSD running on a different IP/container If you have HSD running on a different IP/container
```bash ```bash
sudo docker run -p 5000:5000 -e hsd_api=yourapikeyhere -e hsd_ip=hsdcontainer git.woodburn.au/nathanwoodburn/firewallet:latest sudo docker run -p 5000:5000 -e HSD_API=yourapikeyhere -e HSD_IP=hsdcontainer git.woodburn.au/nathanwoodburn/firewallet:latest
``` ```
For Docker you can mount a volume to persist the user data (/app/user_data) For Docker you can mount a volume to persist the user data (/app/user_data)
@@ -115,11 +115,11 @@ Auction page
## Environment variables ## Environment variables
```yaml ```yaml
hsd_api: HSD API key HSD_API: HSD API key
hsd_ip: HSD IP address HSD_IP: HSD IP address
theme: Theme to use (dark-purple, black) THEME: Theme to use (dark-purple, black)
show_expired: Show expired domains (true/false) SHOW_EXPIRED: Show expired domains (true/false)
exclude: Comma separated list of wallets to exclude from the wallet list EXCLUDE: Comma separated list of wallets to exclude from the wallet list
``` ```

View File

@@ -10,12 +10,12 @@ import time
dotenv.load_dotenv() dotenv.load_dotenv()
HSD_API = os.getenv("hsd_api") HSD_API = os.getenv("HSD_API")
HSD_IP = os.getenv("hsd_ip") HSD_IP = os.getenv("HSD_IP")
if HSD_IP is None: if HSD_IP is None:
HSD_IP = "localhost" HSD_IP = "localhost"
HSD_NETWORK = os.getenv("hsd_network") HSD_NETWORK = os.getenv("HSD_NETWORK")
HSD_WALLET_PORT = 12039 HSD_WALLET_PORT = 12039
HSD_NODE_PORT = 12037 HSD_NODE_PORT = 12037
@@ -35,9 +35,9 @@ elif HSD_NETWORK == "regtest":
HSD_NODE_PORT = 14037 HSD_NODE_PORT = 14037
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 = 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)
@@ -47,9 +47,9 @@ cacheTime = 3600
# Verify the connection # Verify the connection
response = hsd.getInfo() response = hsd.getInfo()
exclude = ["primary"] EXCLUDE = ["primary"]
if os.getenv("exclude") is not None: if os.getenv("EXCLUDE") is not None:
exclude = os.getenv("exclude").split(",") EXCLUDE = os.getenv("EXCLUDE").split(",")
def hsdConnected(): def hsdConnected():
if hsdVersion() == -1: if hsdVersion() == -1:
@@ -163,7 +163,7 @@ def listWallets():
# Check if response is json or an array # Check if response is json or an array
if isinstance(response, list): if isinstance(response, list):
# Remove excluded wallets # Remove excluded wallets
response = [wallet for wallet in response if wallet not in exclude] response = [wallet for wallet in response if wallet not in EXCLUDE]
return response return response
return ['Wallet not connected'] return ['Wallet not connected']
@@ -244,7 +244,7 @@ def getDomains(account,own=True):
response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name") response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name")
info = response.json() info = response.json()
if show_expired: if SHOW_EXPIRED:
return info return info
# Remove any expired domains # Remove any expired domains
@@ -433,6 +433,15 @@ def send(account,address,amount):
"tx": response['result'] "tx": response['result']
} }
def isOwnDomain(account,name: str):
domains = getDomains(account)
for domain in domains:
print(domain)
if domain['name'] == name:
return True
return False
def getDomain(domain: str): def getDomain(domain: str):
# Get the domain # Get the domain
response = hsd.rpc_getNameInfo(domain) response = hsd.rpc_getNameInfo(domain)
@@ -476,7 +485,6 @@ def getDNS(domain: str):
return [] return []
return response['result']['records'] return response['result']['records']
def setDNS(account,domain,records): 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:])
@@ -521,6 +529,9 @@ def setDNS(account,domain,records):
response = hsw.sendUPDATE(account_name,password,domain,data) response = hsw.sendUPDATE(account_name,password,domain,data)
return response return response
def register(account,domain):
# Maybe add default dns records?
return setDNS(account,domain,'[]')
def getNodeSync(): def getNodeSync():
response = hsd.getInfo() response = hsd.getInfo()
@@ -566,6 +577,43 @@ def getBids(account, domain="NONE"):
def getReveals(account,domain): def getReveals(account,domain):
return hsw.getWalletRevealsByName(domain,account) return hsw.getWalletRevealsByName(domain,account)
def getPendingReveals(account):
bids = getBids(account)
domains = getDomains(account,False)
pending = []
for domain in domains:
if domain['state'] == "REVEAL":
reveals = getReveals(account,domain['name'])
for bid in bids:
if bid['name'] == domain['name']:
state_found = False
for reveal in reveals:
if reveal['own'] == True:
if bid['value'] == reveal['value']:
state_found = True
if not state_found:
pending.append(bid)
return pending
#! TODO
def getPendingRedeems(account):
bids = getBids(account)
domains = getDomains(account,False)
pending = []
return pending
def getPendingRegisters(account):
bids = getBids(account)
domains = getDomains(account,False)
pending = []
for domain in domains:
if domain['state'] == "CLOSED" and domain['registered'] == False:
for bid in bids:
if bid['name'] == domain['name']:
if bid['value'] == domain['highest']:
pending.append(bid)
return pending
def getRevealTX(reveal): def getRevealTX(reveal):
prevout = reveal['prevout'] prevout = reveal['prevout']
@@ -573,7 +621,11 @@ def getRevealTX(reveal):
index = prevout['index'] index = prevout['index']
tx = hsd.getTxByHash(hash) tx = hsd.getTxByHash(hash)
if 'inputs' not in tx: if 'inputs' not in tx:
# Check if registered print(f'Something is up with this tx: {hash}')
print(tx)
print('---')
# No idea what happened here
# Check if registered?
return None return None
return tx['inputs'][index]['prevout']['hash'] return tx['inputs'][index]['prevout']['hash']
@@ -616,7 +668,6 @@ def revealAll(account):
response = hsw.rpc_walletPassphrase(password,10) response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None: if response['error'] is not None:
return return
# Try to send the batch of all renew, reveal and redeem actions
return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",json={"method": "sendbatch","params": [[["REVEAL"]]]}).json() return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",json={"method": "sendbatch","params": [[["REVEAL"]]]}).json()
except Exception as e: except Exception as e:
@@ -626,6 +677,58 @@ def revealAll(account):
} }
} }
def redeemAll(account):
account_name = check_account(account)
password = ":".join(account.split(":")[1:])
if account_name == False:
return {
"error": {
"message": "Invalid account"
}
}
try:
# Try to select and login to the wallet
response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None:
return
response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None:
return
return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",json={"method": "sendbatch","params": [[["REDEEM"]]]}).json()
except Exception as e:
return {
"error": {
"message": str(e)
}
}
def registerAll(account):
account_name = check_account(account)
password = ":".join(account.split(":")[1:])
if account_name == False:
return {
"error": {
"message": "Invalid account"
}
}
# try:
domains = getPendingRegisters(account_name)
if len(domains) == 0:
return {
"error": {
"message": "Nothing to do."
}
}
batch = []
for domain in domains:
batch.append(["UPDATE",domain['name'],{"records":[]}])
return sendBatch(account,batch)
def rescan_auction(account,domain): def rescan_auction(account,domain):
# Get height of the start of the auction # Get height of the start of the auction
response = hsw.rpc_selectWallet(account) response = hsw.rpc_selectWallet(account)

View File

@@ -1,4 +1,5 @@
hsd_api=123480615465636893475aCwyaae6s45 HSD_API=123480615465636893475aCwyaae6s45
hsd_ip=localhost HSD_IP=localhost
theme=black THEME=black
show_expired=false SHOW_EXPIRED=false
EXPLORER_TX=https://niami.io/tx/

110
main.py
View File

@@ -26,7 +26,7 @@ fees = 0.02
revokeCheck = random.randint(100000,999999) revokeCheck = random.randint(100000,999999)
theme = os.getenv("theme") THEME = os.getenv("THEME")
@app.route('/') @app.route('/')
def index(): def index():
@@ -322,19 +322,7 @@ def auctions():
bidsHtml = render.bidDomains(bids,domains) bidsHtml = render.bidDomains(bids,domains)
pending_reveals = 0
for domain in domains:
if domain['state'] == "REVEAL":
for bid in bids:
if bid['name'] == domain['name']:
bid_found = False
reveals = account_module.getReveals(account,domain['name'])
for reveal in reveals:
if reveal['own'] == True:
if bid['value'] == reveal['value']:
bid_found = True
if not bid_found:
pending_reveals += 1
plugins = "" plugins = ""
@@ -347,10 +335,11 @@ def auctions():
sort_state=sort_state,sort_domain=sort_domain, sort_state=sort_state,sort_domain=sort_domain,
sort_price_next=sort_price_next, sort_price_next=sort_price_next,
sort_state_next=sort_state_next,sort_domain_next=sort_domain_next, sort_state_next=sort_state_next,sort_domain_next=sort_domain_next,
bids=len(bids),reveal=pending_reveals,message=message, bids=len(bids),message=message,
sort_time=sort_time,sort_time_next=sort_time_next) sort_time=sort_time,sort_time_next=sort_time_next)
@app.route('/reveal') @app.route('/reveal')
@app.route('/all/reveal')
def revealAllBids(): def revealAllBids():
# Check if the user is logged in # Check if the user is logged in
if request.cookies.get("account") is None: if request.cookies.get("account") is None:
@@ -371,6 +360,46 @@ def revealAllBids():
return redirect("/success?tx=" + response['result']['hash']) return redirect("/success?tx=" + response['result']['hash'])
@app.route('/all/redeem')
def redeemAllBids():
# Check if the user is logged in
if request.cookies.get("account") is None:
return redirect("/login")
account = account_module.check_account(request.cookies.get("account"))
if not account:
return redirect("/logout")
response = account_module.redeemAll(request.cookies.get("account"))
if 'error' in response:
print(response)
if response['error'] != None:
if response['error']['message'] == "Nothing to do.":
return redirect("/auctions?message=No redeems pending")
return redirect("/auctions?message=" + response['error']['message'])
return redirect("/success?tx=" + response['result']['hash'])
@app.route('/all/register')
def registerAllDomains():
# Check if the user is logged in
if request.cookies.get("account") is None:
return redirect("/login")
account = account_module.check_account(request.cookies.get("account"))
if not account:
return redirect("/logout")
response = account_module.registerAll(request.cookies.get("account"))
if 'error' in response:
print(response)
if response['error'] != None:
if response['error']['message'] == "Nothing to do.":
return redirect("/auctions?message=No domains to register")
return redirect("/auctions?message=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
@app.route('/search') @app.route('/search')
def search(): def search():
# Check if the user is logged in # Check if the user is logged in
@@ -845,7 +874,11 @@ def auction(domain):
error=error) error=error)
if domainInfo['info'] is None: if domainInfo['info'] is None:
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>' if domainInfo['registered'] == False and domainInfo['expired'] == False:
# Needs to be registered
next_action = f'ERROR GETTING NEXT STATE'
else:
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,
search_term=search_term,domain=search_term,next_action=next_action, search_term=search_term,domain=search_term,next_action=next_action,
@@ -871,9 +904,17 @@ def auction(domain):
if state == 'CLOSED': if state == 'CLOSED':
if not domainInfo['info']['registered']: if not domainInfo['info']['registered']:
state = 'AVAILABLE' if account_module.isOwnDomain(account,domain):
next = "Available Now" print("Waiting to be registered")
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>' state = 'PENDING REGISTER'
next = "Pending Register"
next_action = f'<a href="/auction/{domain}/register">Register Domain</a>'
else:
print("Not registered")
state = 'AVAILABLE'
next = "Available Now"
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
else: else:
state = 'REGISTERED' state = 'REGISTERED'
expires = domainInfo['info']['stats']['daysUntilExpire'] expires = domainInfo['info']['stats']['daysUntilExpire']
@@ -1030,7 +1071,22 @@ def reveal_auction(domain):
return redirect("/logout") return redirect("/logout")
domain = domain.lower() domain = domain.lower()
response = account_module.revealAuction(request.cookies.get("account"),domain) response = account_module(request.cookies.get("account"),domain)
if 'error' in response:
return redirect("/auction/" + domain + "?message=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
@app.route('/auction/<domain>/register')
def registerdomain(domain):
# Check if the user is logged in
if request.cookies.get("account") is None:
return redirect("/login")
if not account_module.check_account(request.cookies.get("account")):
return redirect("/logout")
domain = domain.lower()
response = account_module.register(request.cookies.get("account"),domain)
if 'error' in response: if 'error' in response:
return redirect("/auction/" + domain + "?message=" + response['error']['message']) return redirect("/auction/" + domain + "?message=" + response['error']['message'])
return redirect("/success?tx=" + response['hash']) return redirect("/success?tx=" + response['hash'])
@@ -1449,6 +1505,16 @@ def api_wallet(function):
if function == "domainCount": if function == "domainCount":
return jsonify({"result": len(account_module.getDomains(account))}) return jsonify({"result": len(account_module.getDomains(account))})
if function == "bidCount":
return jsonify({"result": len(account_module.getBids(account))})
if function == "pendingReveal":
return jsonify({"result": len(account_module.getPendingReveals(account))})
if function == "pendingRegister":
return jsonify({"result": len(account_module.getPendingRegisters(account))})
if function == "pendingRedeem":
return jsonify({"result": len(account_module.getPendingRedeems(account))})
return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400 return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400
@@ -1466,9 +1532,9 @@ def qr(data):
# Theme # Theme
@app.route('/assets/css/styles.min.css') @app.route('/assets/css/styles.min.css')
def send_css(): def send_css():
if theme == "live": if THEME == "live":
return send_from_directory('templates/assets/css', 'styles.min.css') return send_from_directory('templates/assets/css', 'styles.min.css')
return send_from_directory('themes', f'{theme}.css') return send_from_directory('themes', f'{THEME}.css')
@app.route('/assets/<path:path>') @app.route('/assets/<path:path>')
def send_assets(path): def send_assets(path):

View File

@@ -3,6 +3,14 @@ import json
import urllib.parse import urllib.parse
from flask import render_template from flask import render_template
from domainLookup import punycode_to_emoji from domainLookup import punycode_to_emoji
import os
# Get Explorer URL
TX_EXPLORER_URL = os.getenv("EXPLORER_TX")
if TX_EXPLORER_URL is None:
TX_EXPLORER_URL = "https://niami.io/tx/"
def domains(domains, mobile=False): def domains(domains, mobile=False):
html = '' html = ''
@@ -21,10 +29,17 @@ def domains(domains, mobile=False):
if emoji != name: if emoji != name:
name = f'{emoji} ({name})' name = f'{emoji} ({name})'
link = f'/manage/{domain["name"]}'
link_action = "Manage"
if domain['registered'] == False:
link_action = "Register"
link = f'/auction/{domain["name"]}/register'
if not mobile: if not mobile:
html += f'<tr><td>{name}</td><td>{expires} days</td><td>{paid} HNS</td><td><a href="/manage/{domain["name"]}">Manage</a></td></tr>' html += f'<tr><td>{name}</td><td>{expires} days</td><td>{paid:,.2f} HNS</td><td><a href="{link}">{link_action}</a></td></tr>'
else: else:
html += f'<tr><td><a href="/manage/{domain["name"]}">{name}</a></td><td>{expires} days</td></tr>' html += f'<tr><td><a href="{link}">{name}</a></td><td>{expires} days</td></tr>'
return html return html
@@ -58,17 +73,15 @@ def transactions(txs):
amount += output["value"] amount += output["value"]
amount = amount / 1000000 amount = amount / 1000000
amount = round(amount, 2)
amount = "{:,}".format(amount)
hash = "<a target='_blank' href='https://niami.io/tx/" + hash + "'>" + hash[:8] + "...</a>" hash = f"<a target='_blank' href='{TX_EXPLORER_URL}{hash}'>{hash[:8]}...</a>"
if confirmations < 5: if confirmations < 5:
confirmations = "<td style='background-color: red;'>" + str(confirmations) + "</td>" confirmations = f"<td style='background-color: red;'>{confirmations}</td>"
else: else:
confirmations = "<td>" + str(confirmations) + "</td>" confirmations = f"<td>{confirmations:,}</td>"
html += f'<tr><td>{action}</td><td>{address}</td><td>{hash}</td>{confirmations}<td>{amount} HNS</td></tr>' html += f'<tr><td>{action}</td><td>{address}</td><td>{hash}</td>{confirmations}<td>{amount:,.2f} HNS</td></tr>'
return html return html
@@ -92,14 +105,14 @@ def dns(data, edit=False):
elif entry['type'] == 'DS': elif entry['type'] == 'DS':
ds = str(entry['keyTag']) + " " + str(entry['algorithm']) + " " + str(entry['digestType']) + " " + entry['digest'] ds = f'{entry['keyTag']} {entry['algorithm']} {entry['digestType']} {entry['digest']}'
html_output += f"<td>{ds}</td>\n" html_output += f"<td>{ds}</td>\n"
else: else:
value = "" value = ""
for key, val in entry.items(): for key, val in entry.items():
if key != 'type': if key != 'type':
value += str(val) + " " value += f'{val} '
html_output += f"<td>{value}</td>\n" html_output += f"<td>{value}</td>\n"
if edit: if edit:
@@ -119,18 +132,16 @@ def txs(data):
for entry in data: for entry in data:
html_output += f"<tr><td>{entry['action']}</td>\n" html_output += f"<tr><td>{entry['action']}</td>\n"
html_output += f"<td><a target='_blank' href='https://niami.io/tx/{entry['txid']}'>{entry['txid'][:8]}...</a></td>\n" html_output += f"<td><a target='_blank' href='{TX_EXPLORER_URL}{entry['txid']}'>{entry['txid'][:8]}...</a></td>\n"
amount = entry['amount'] amount = entry['amount']
amount = amount / 1000000 amount = amount / 1000000
amount = round(amount, 2)
if entry['blind'] == None: if entry['blind'] == None:
html_output += f"<td>{amount} HNS</td>\n" html_output += f"<td>{amount:,.2f} HNS</td>\n"
else: else:
blind = entry['blind'] blind = entry['blind']
blind = blind / 1000000 blind = blind / 1000000
blind = round(blind, 2) html_output += f"<td>{amount:,.2f} + {blind:,.2f} HNS</td>\n"
html_output += f"<td>{amount} + {blind} 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 += f"</tr>\n"
@@ -149,20 +160,17 @@ def bids(bids,reveals):
for bid in bids: for bid in bids:
lockup = bid['lockup'] lockup = bid['lockup']
lockup = lockup / 1000000 lockup = lockup / 1000000
lockup = round(lockup, 2)
html += "<tr>" html += "<tr>"
html += f"<td>{lockup} HNS</td>" html += f"<td>{lockup:,.2f} HNS</td>"
revealed = False revealed = False
for reveal in reveals: for reveal in reveals:
if reveal['bid'] == bid['prevout']['hash']: if reveal['bid'] == bid['prevout']['hash']:
revealed = True revealed = True
value = reveal['value'] value = reveal['value']
value = value / 1000000 value = value / 1000000
value = round(value, 2) html += f"<td>{value:,.2f} HNS</td>"
html += f"<td>{value} HNS</td>"
bidValue = lockup - value bidValue = lockup - value
bidValue = round(bidValue, 2) html += f"<td>{bidValue:,.2f} HNS</td>"
html += f"<td>{bidValue} HNS</td>"
break break
if not revealed: if not revealed:
html += f"<td>Hidden until reveal</td>" html += f"<td>Hidden until reveal</td>"
@@ -183,22 +191,17 @@ def bidDomains(bids,domains, sortState=False):
if bid['name'] == domain['name']: if bid['name'] == domain['name']:
lockup = bid['lockup'] lockup = bid['lockup']
lockup = lockup / 1000000 lockup = lockup / 1000000
lockup = round(lockup, 2)
bidValue = bid['value'] / 1000000 bidValue = bid['value'] / 1000000
bidValue = round(bidValue, 2)
blind = lockup - bidValue blind = lockup - bidValue
bidValue = "{:,}".format(bidValue)
blind = round(blind, 2)
blind = "{:,}".format(blind)
bidDisplay = f'<b>{bidValue} HNS</b> + {blind} HNS blind' bidDisplay = f'<b>{bidValue:,.2f} HNS</b> + {blind:,.2f} HNS blind'
html += "<tr>" html += "<tr>"
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']}'>{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']}'>{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>{bid['height']}</td>" html += f"<td>{bid['height']:,}</td>"
html += "</tr>" html += "</tr>"
else: else:
for domain in domains: for domain in domains:
@@ -206,19 +209,15 @@ def bidDomains(bids,domains, sortState=False):
if bid['name'] == domain['name']: if bid['name'] == domain['name']:
lockup = bid['lockup'] lockup = bid['lockup']
lockup = lockup / 1000000 lockup = lockup / 1000000
lockup = round(lockup, 2)
bidValue = bid['value'] / 1000000 bidValue = bid['value'] / 1000000
bidValue = round(bidValue, 2)
blind = lockup - bidValue blind = lockup - bidValue
bidValue = "{:,}".format(bidValue)
blind = "{:,}".format(blind)
bidDisplay = f'<b>{bidValue} HNS</b> + {blind} HNS blind' bidDisplay = f'<b>{bidValue:,.2f} HNS</b> + {blind:,.2f} HNS blind'
html += "<tr>" html += "<tr>"
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']}'>{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']}'>{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>{domain['height']}</td>" html += f"<td>{domain['height']:,}</td>"
html += "</tr>" html += "</tr>"
return html return html

View File

@@ -1 +1 @@
async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const o=await t.json();return void 0!==o.error?`Error: ${o.error}`:o.result}catch(e){return console.error("Request failed:",e),"Error"}}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount"],t=["wallet-available","wallet-total","wallet-locked","wallet-pending"];for(const o of e){const e=document.getElementById(o);if(e){const l=o.replace(/-/g,"/");let n=await request(l);t.includes(o)&&(n=Number(n).toFixed(2),n=n.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")),e.innerHTML=n}}})),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var o=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var l of t)l.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var l of o)l.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of o)e.hide()}))}var n=document.querySelector("body.fixed-nav .sidebar");n&&n.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,o=t.wheelDelta||-t.detail;this.scrollTop+=30*(o<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}(); async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const l=await t.json();return void 0!==l.error?`Error: ${l.error}`:l.result}catch(e){return console.error("Request failed:",e),"Error"}}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked","wallet-pending"];for(const l of e){const e=document.getElementById(l);if(e){const o=l.replace(/-/g,"/");let n=await request(o);t.includes(l)&&(n=Number(n).toFixed(2),n=n.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")),e.innerHTML=n}}})),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var l=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var o of t)o.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var o of l)o.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of l)e.hide()}))}var n=document.querySelector("body.fixed-nav .sidebar");n&&n.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,l=t.wheelDelta||-t.detail;this.scrollTop+=30*(l<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}();

View File

@@ -87,7 +87,7 @@
<div class="row align-items-center no-gutters"> <div class="row align-items-center no-gutters">
<div class="col me-2"> <div class="col me-2">
<div class="text-uppercase text-success fw-bold text-xs mb-1"><span>Total Bids</span></div> <div class="text-uppercase text-success fw-bold text-xs mb-1"><span>Total Bids</span></div>
<div class="text-dark fw-bold h5 mb-0"><span>{{bids}}</span></div> <div class="text-dark fw-bold h5 mb-0"><span id="wallet-bidCount">0</span></div>
</div> </div>
</div> </div>
</div> </div>
@@ -101,9 +101,43 @@
<div class="text-uppercase text-info fw-bold text-xs mb-1"><span>Pending Reveal</span></div> <div class="text-uppercase text-info fw-bold text-xs mb-1"><span>Pending Reveal</span></div>
<div class="row g-0 align-items-center"> <div class="row g-0 align-items-center">
<div class="col-auto"> <div class="col-auto">
<div class="text-dark fw-bold h5 mb-0 me-3"><span>{{reveal}}</span></div> <div class="text-dark fw-bold h5 mb-0 me-3"><span id="wallet-pendingReveal">0</span></div>
</div> </div>
<div class="col"><a class="btn btn-primary" role="button" href="/reveal">Reveal All</a></div> <div class="col"><a class="btn btn-primary" role="button" href="/all/reveal">Reveal All</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3 mb-4">
<div class="card shadow border-start-info py-2">
<div class="card-body">
<div class="row align-items-center no-gutters">
<div class="col me-2">
<div class="text-uppercase text-info fw-bold text-xs mb-1"><span>Pending Redeem</span></div>
<div class="row g-0 align-items-center">
<div class="col-auto">
<div class="text-dark fw-bold h5 mb-0 me-3"><span id="wallet-pendingRedeem">0</span></div>
</div>
<div class="col"><a class="btn btn-primary" role="button" href="/all/redeem">Redeem All</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3 mb-4">
<div class="card shadow border-start-info py-2">
<div class="card-body">
<div class="row align-items-center no-gutters">
<div class="col me-2">
<div class="text-uppercase text-info fw-bold text-xs mb-1"><span>Pending Register</span></div>
<div class="row g-0 align-items-center">
<div class="col-auto">
<div class="text-dark fw-bold h5 mb-0 me-3"><span id="wallet-pendingRegister">0</span></div>
</div>
<div class="col"><a class="btn btn-primary" role="button" href="/all/register">Register All</a></div>
</div> </div>
</div> </div>
</div> </div>