6 Commits

Author SHA1 Message Date
792688064e fix: More code cleanup
All checks were successful
Build Docker / Build Image (push) Successful in 2m9s
2025-08-25 12:43:12 +10:00
599c0df00c feat: Add more code cleanup
All checks were successful
Build Docker / Build Image (push) Successful in 51s
2025-08-25 12:36:11 +10:00
a619d78efd fix: Update types to make code more reliable 2025-08-25 12:28:26 +10:00
f090b7b71a feat: Add initial WALLET DNS record support
All checks were successful
Build Docker / Build Image (push) Successful in 2m46s
2025-08-25 11:55:15 +10:00
545a0b9475 fix: Add display for OPEN transactions in tx history
All checks were successful
Build Docker / Build Image (push) Successful in 2m44s
2025-08-18 11:33:47 +10:00
501091eeae Merge pull request 'feat/mempool-bids' (#2) from feat/mempool-bids into main
All checks were successful
Build Docker / Build Image (push) Successful in 2m19s
Reviewed-on: #2
2025-07-17 16:54:38 +10:00
6 changed files with 188 additions and 106 deletions

View File

@@ -10,10 +10,8 @@ import time
dotenv.load_dotenv()
HSD_API = os.getenv("HSD_API")
HSD_IP = os.getenv("HSD_IP")
if HSD_IP is None:
HSD_IP = "localhost"
HSD_API = os.getenv("HSD_API","")
HSD_IP = os.getenv("HSD_IP","localhost")
HSD_NETWORK = os.getenv("HSD_NETWORK")
HSD_WALLET_PORT = 12039
@@ -47,9 +45,7 @@ cacheTime = 3600
# Verify the connection
response = hsd.getInfo()
EXCLUDE = ["primary"]
if os.getenv("EXCLUDE") is not None:
EXCLUDE = os.getenv("EXCLUDE").split(",")
EXCLUDE = os.getenv("EXCLUDE","primary").split(",")
def hsdConnected():
@@ -68,7 +64,7 @@ def hsdVersion(format=True):
return info['version']
def check_account(cookie: str):
def check_account(cookie: str | None):
if cookie is None:
return False
@@ -84,7 +80,12 @@ def check_account(cookie: str):
return account
def check_password(cookie: str, password: str):
def check_password(cookie: str|None, password: str|None):
if cookie is None:
return False
if password is None:
password = ""
account = check_account(cookie)
if account == False:
return False
@@ -403,17 +404,30 @@ def check_hip2(domain: str):
return 'Invalid domain'
address = domainLookup.hip2(domain)
if address.startswith("Hip2: "):
if not address.startswith("Hip2: "):
if not check_address(address, False, True):
return 'Hip2: Lookup succeeded but address is invalid'
return address
# Try using WALLET TXT record
address = domainLookup.wallet_txt(domain)
if not address.startswith("hs1"):
return "No HIP2 or WALLET record found for this domain"
if not check_address(address, False, True):
return 'Hip2: Lookup succeeded but address is invalid'
return 'WALLET DNS record found but address is invalid'
return address
def send(account, address, amount):
account_name = check_account(account)
password = ":".join(account.split(":")[1:])
if not account_name:
return {
"error": {
"message": "Invalid account"
}
}
response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None:
return {
@@ -718,9 +732,13 @@ def getPendingFinalizes(account, password):
pending = []
try:
for output in tx['outputs']:
if output['covenant']['type'] != 10:
if type(output) != dict:
continue
if output['covenant']['action'] != "FINALIZE":
if not 'covenant' in output:
continue
if output['covenant'].get("type") != 10:
continue
if output['covenant'].get('action') != "FINALIZE":
continue
nameHash = output['covenant']['items'][0]
# Try to get the name from hash

View File

@@ -6,10 +6,14 @@ import subprocess
import binascii
import datetime
import dns.asyncresolver
import dns.message
import dns.query
import dns.rdatatype
import httpx
from requests_doh import DNSOverHTTPSSession, add_dns_provider
import requests
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)
@@ -56,7 +60,7 @@ def hip2(domain: str):
domains = []
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)
domains.extend(san_list)
@@ -120,13 +124,39 @@ def hip2(domain: str):
print(f"Hip2: Lookup failed with error: {e}",flush=True)
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"):
with httpx.Client() as client:
q = dns.message.make_query(query_name, dns.rdatatype.A)
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
def resolve_TLSA_with_doh(query_name, doh_url="https://hnsdoh.com/dns-query"):

182
main.py
View File

@@ -66,7 +66,6 @@ def index():
# 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")
@@ -114,7 +113,7 @@ def transactions():
return redirect("/logout")
# Get the page parameter
page = request.args.get('page')
page = request.args.get('page', 1)
try:
page = int(page)
except:
@@ -135,6 +134,8 @@ def send_page():
return redirect("/login")
account = account_module.check_account(request.cookies.get("account"))
if not account:
return redirect("/logout")
max = account_module.getBalance(account)['available']
# Subtract approx fee
max = max - fees
@@ -170,28 +171,28 @@ def send():
amount = request.form.get("amount")
if address is None or amount is None:
return redirect("/send?message=Invalid address or amount&address=" + address + "&amount=" + amount)
return redirect(f"/send?message=Invalid address or amount&address={address}&amount={amount}")
address_check = account_module.check_address(address.strip(),True,True)
if not address_check:
return redirect("/send?message=Invalid address&address=" + address + "&amount=" + amount)
return redirect(f"/send?message=Invalid address&address={address}&amount={amount}")
address = address_check
# Check if the amount is valid
if re.match(r"^\d+(\.\d+)?$", amount) is None:
return redirect("/send?message=Invalid amount&address=" + address + "&amount=" + amount)
return redirect(f"/send?message=Invalid amount&address={address}&amount={amount}")
# Check if the amount is valid
amount = float(amount)
if amount <= 0:
return redirect("/send?message=Invalid amount&address=" + address + "&amount=" + str(amount))
return redirect(f"/send?message=Invalid amount&address={address}&amount={amount}")
if amount > account_module.getBalance(account)['available'] - fees:
return redirect("/send?message=Not enough funds to transfer&address=" + address + "&amount=" + str(amount))
return redirect(f"/send?message=Not enough funds to transfer&address={address}&amount={amount}")
toAddress = address
if request.form.get('address') != address:
toAddress = request.form.get('address') + "<br>" + address
toAddress = f"{request.form.get('address')}<br>{address}"
action = f"Send HNS to {request.form.get('address')}"
content = f"Are you sure you want to send {amount} HNS to {toAddress}<br><br>"
@@ -202,7 +203,6 @@ def send():
return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")),
action=action,
content=content,cancel=cancel,confirm=confirm)
@@ -211,20 +211,20 @@ def send():
def sendConfirmed():
address = request.args.get("address")
amount = float(request.args.get("amount"))
amount = float(request.args.get("amount","0"))
response = account_module.send(request.cookies.get("account"),address,amount)
if 'error' in response and response['error'] != None:
# If error is a dict get the message
if isinstance(response['error'], dict):
if 'message' in response['error']:
return redirect("/send?message=" + response['error']['message'] + "&address=" + address + "&amount=" + str(amount))
return redirect(f"/send?message={response['error']['message']}&address={address}&amount={amount}")
else:
return redirect("/send?message=" + str(response['error']) + "&address=" + address + "&amount=" + str(amount))
return redirect(f"/send?message={response['error']}&address={address}&amount={amount}")
# If error is a string
return redirect("/send?message=" + response['error'] + "&address=" + address + "&amount=" + str(amount))
return redirect(f"/send?message={response['error']}&address={address}&amount={amount}")
return redirect("/success?tx=" + response['tx'])
return redirect(f"/success?tx={response['tx']}")
@@ -363,6 +363,9 @@ def revealAllBids():
return redirect("/logout")
response = account_module.revealAll(request.cookies.get("account"))
if not response:
return redirect("/auctions?message=Failed to reveal bids")
if 'error' in response:
if response['error'] != None:
if response['error']['message'] == "Nothing to do.":
@@ -383,6 +386,9 @@ def redeemAllBids():
return redirect("/logout")
response = account_module.redeemAll(request.cookies.get("account"))
if not response:
return redirect("/auctions?message=Failed to redeem bids")
if 'error' in response:
if response['error'] != None:
if response['error']['message'] == "Nothing to do.":
@@ -402,13 +408,16 @@ def registerAllDomains():
return redirect("/logout")
response = account_module.registerAll(request.cookies.get("account"))
if not response:
return redirect("/auctions?message=Failed to register domains")
if 'error' in 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'])
return redirect(f"/success?tx={response['hash']}")
@app.route('/all/finalize')
def finalizeAllBids():
@@ -427,7 +436,7 @@ def finalizeAllBids():
return redirect("/dashboard?message=No domains to finalize")
return redirect("/dashboard?message=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
return redirect(f"/success?tx={response['hash']}")
#endregion
@app.route('/search')
@@ -441,6 +450,8 @@ def search():
return redirect("/logout")
search_term = request.args.get("q")
if search_term is None:
return redirect("/")
search_term = search_term.lower().strip()
# Replace spaces with hyphens
@@ -457,7 +468,7 @@ def search():
# Execute domain plugins
searchFunctions = plugins_module.getSearchFunctions()
for function in searchFunctions:
functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":search_term},account_module.check_account(request.cookies.get("account")))
functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":search_term},account)
plugins += render.plugin_output(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"]))
plugins += "</div>"
@@ -475,6 +486,7 @@ def search():
state = domain['info']['state']
stats = domain['info']['stats']
next = ""
if state == 'CLOSED':
if domain['info']['registered']:
state = 'REGISTERED'
@@ -571,7 +583,7 @@ def manage(domain: str):
# Execute domain plugins
domainFunctions = plugins_module.getDomainFunctions()
for function in domainFunctions:
functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":domain},account_module.check_account(request.cookies.get("account")))
functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":domain},account)
plugins += render.plugin_output(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"]))
plugins += "</div>"
@@ -676,7 +688,7 @@ def revokeConfirm(domain: str):
print(response)
return redirect("/manage/" + domain + "?error=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
return redirect(f"/success?tx={response['hash']}")
@app.route('/manage/<domain>/renew')
def renew(domain: str):
@@ -690,7 +702,7 @@ def renew(domain: str):
domain = domain.lower()
response = account_module.renewDomain(request.cookies.get("account"),domain)
return redirect("/success?tx=" + response['hash'])
return redirect(f"/success?tx={response['hash']}")
@app.route('/manage/<domain>/edit')
def editPage(domain: str):
@@ -716,8 +728,11 @@ def editPage(domain: str):
dns = urllib.parse.unquote(user_edits)
else:
dns = account_module.getDNS(domain)
dns = json.loads(dns)
if dns and isinstance(dns, str):
dns = json.loads(dns)
else:
dns = []
# Check if new records have been added
dnsType = request.args.get("type")
@@ -733,14 +748,14 @@ def editPage(domain: str):
return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record")
try:
ds[0] = int(ds[0])
ds[1] = int(ds[1])
ds[2] = int(ds[2])
key_tag = int(ds[0])
algorithm = int(ds[1])
digest_type = int(ds[2])
except:
raw_dns = str(dns).replace("'",'"')
return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record")
finally:
dns.append({"type": dnsType, "keyTag": ds[0], "algorithm": ds[1], "digestType": ds[2], "digest": ds[3]})
dns.append({"type": dnsType, "keyTag": key_tag, "algorithm": algorithm, "digestType": digest_type, "digest": ds[3]})
dns = json.dumps(dns).replace("'",'"')
return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(dns))
@@ -770,13 +785,15 @@ def editSave(domain: str):
domain = domain.lower()
dns = request.args.get("dns")
if dns is None:
return redirect(f"/manage/{domain}/edit?error=No DNS records provided")
raw_dns = dns
dns = urllib.parse.unquote(dns)
response = account_module.setDNS(request.cookies.get("account"),domain,dns)
if 'error' in response:
print(response)
return redirect("/manage/" + domain + "/edit?dns="+raw_dns+"&error=" + str(response['error']))
return redirect("/success?tx=" + response['hash'])
return redirect(f"/manage/{domain}/edit?dns={raw_dns}&error={response['error']}")
return redirect(f"/success?tx={response['hash']}")
@app.route('/manage/<domain>/transfer')
def transfer(domain):
@@ -801,7 +818,7 @@ def transfer(domain):
toAddress = address
if request.form.get('address') != address:
toAddress = request.args.get('address') + "<br>" + address
toAddress = f"{request.args.get('address')}<br>{address}"
action = f"Send {domain}/ to {request.form.get('address')}"
content = f"Are you sure you want to send {domain}/ to {toAddress}<br><br>"
@@ -811,9 +828,7 @@ def transfer(domain):
confirm = f"/manage/{domain}/transfer/confirm?address={address}"
return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")),
action=action,
return render_template("confirm.html", account=account,action=action,
content=content,cancel=cancel,confirm=confirm)
@app.route('/manage/<domain>/sign')
@@ -836,7 +851,7 @@ def signMessage(domain):
signedMessage = account_module.signMessage(request.cookies.get("account"),domain,message)
if signedMessage["error"] != None:
return redirect("/manage/" + domain + "?error=" + signedMessage["error"])
content += "Signature:<br><code>" + signedMessage["result"] + "</code><br><br>"
content += f"Signature:<br><code>{signedMessage["result"]}</code><br><br>"
data = {
"domain": domain,
@@ -853,8 +868,7 @@ def signMessage(domain):
return render_template("message.html", account=account,
return render_template("message.html", account=account,
title="Sign Message",content=content)
@@ -873,7 +887,7 @@ def transferConfirm(domain):
if 'error' in response:
return redirect("/manage/" + domain + "?error=" + response['error'])
return redirect("/success?tx=" + response['hash'])
return redirect(f"/success?tx={response['hash']}")
@app.route('/auction/<domain>')
@@ -917,20 +931,9 @@ def auction(domain):
state = domainInfo['info']['state']
next_action = ''
next = ""
# bids = account_module.getBids(account,search_term)
bids = []
# if bids == []:
# bids = "No bids found"
# next_action = f'<a href="/auction/{domain}/scan">Rescan Auction</a>'
# else:
# reveals = account_module.getReveals(account,search_term)
# for reveal in reveals:
# # Get TX
# revealInfo = account_module.getRevealTX(reveal)
# reveal['bid'] = revealInfo
# bids = render.bids(bids,reveals)
stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
if state == 'CLOSED':
if not domainInfo['info']['registered']:
@@ -1010,8 +1013,8 @@ def bid(domain):
return redirect("/logout")
domain = domain.lower()
bid = request.args.get("bid")
blind = request.args.get("blind")
bid = request.args.get("bid","")
blind = request.args.get("blind","")
if bid == "":
bid = 0
@@ -1056,8 +1059,8 @@ def bid_confirm(domain):
return redirect("/logout")
domain = domain.lower()
bid = request.args.get("bid")
blind = request.args.get("blind")
bid = request.args.get("bid","")
blind = request.args.get("blind","")
if bid == "":
bid = 0
@@ -1076,7 +1079,7 @@ def bid_confirm(domain):
if 'error' in response:
return redirect("/auction/" + domain + "?error=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
return redirect(f"/success?tx={response['hash']}")
@app.route('/auction/<domain>/open')
def open_auction(domain):
@@ -1095,7 +1098,7 @@ def open_auction(domain):
if response['error'] != None:
return redirect("/auction/" + domain + "?error=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
return redirect(f"/success?tx={response['hash']}")
@app.route('/auction/<domain>/reveal')
def reveal_auction(domain):
@@ -1110,8 +1113,8 @@ def reveal_auction(domain):
domain = domain.lower()
response = account_module.revealAuction(request.cookies.get("account"),domain)
if 'error' in response:
return redirect("/auction/" + domain + "?message=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
return redirect(f"/auction/{domain}?message={response['error']}")
return redirect(f"/success?tx={response['hash']}")
@app.route('/auction/<domain>/register')
def registerdomain(domain):
@@ -1126,7 +1129,7 @@ def registerdomain(domain):
response = account_module.register(request.cookies.get("account"),domain)
if 'error' in response:
return redirect("/auction/" + domain + "?message=" + response['error']['message'])
return redirect("/success?tx=" + response['hash'])
return redirect(f"/success?tx={response['hash']}")
#endregion
#region Settings
@@ -1153,6 +1156,11 @@ def settings():
hsd_version=account_module.hsdVersion(False),
error=error,success=success,version="Error")
info = gitinfo.get_git_info()
if not info:
return render_template("settings.html", account=account,
hsd_version=account_module.hsdVersion(False),
error=error,success=success,version="Error")
branch = info['refs']
if branch != "main":
branch = f"({branch})"
@@ -1192,20 +1200,19 @@ def settings_action(action):
elif action == "zap":
resp = account_module.zapTXs(request.cookies.get("account"))
if 'error' in resp:
if type(resp) == dict and 'error' in resp:
return redirect("/settings?error=" + str(resp['error']))
return redirect("/settings?success=Zapped transactions")
elif action == "xpub":
xpub = account_module.getxPub(request.cookies.get("account"))
content = "<br><br>"
content += "<textarea style='display: none;' id='data' rows='4' cols='50'>"+xpub+"</textarea>"
content += f"<textarea style='display: none;' id='data' rows='4' cols='50'>{xpub}</textarea>"
content += "<script>function copyToClipboard() {var copyText = document.getElementById('data');copyText.style.display = 'block';copyText.select();copyText.setSelectionRange(0, 99999);document.execCommand('copy');copyText.style.display = 'none';var copyButton = document.getElementById('copyButton');copyButton.innerHTML='Copied';}</script>"
content += "<button id='copyButton' onclick='copyToClipboard()' class='btn btn-secondary'>Copy to clipboard</button>"
return render_template("message.html", account=account,
title="xPub Key",
content="<code>"+xpub+"</code>" + content)
content=f"<code>{xpub}</code>{content}")
return redirect("/settings?error=Invalid action")
@@ -1215,6 +1222,9 @@ def upload_image():
return redirect("/login?message=Not logged in")
account = request.cookies.get("account")
account = account_module.check_account(account)
if not account:
return redirect("/logout")
if not os.path.exists('user_data/images'):
os.mkdir('user_data/images')
@@ -1224,11 +1234,12 @@ def upload_image():
file = request.files['image']
if file.filename == '':
return redirect("/settings?error=No file selected")
if file:
filepath = os.path.join(f'user_data/images/{account.split(":")[0]}.{file.filename.split(".")[-1]}')
if file and file.filename:
filepath = os.path.join(f'user_data/images/{account}.{file.filename.split(".")[-1]}')
file.save(filepath)
return redirect("/settings?success=File uploaded successfully")
return redirect("/settings?error=An error occurred")
def latestVersion(branch):
result = requests.get(f"https://git.woodburn.au/api/v1/repos/nathanwoodburn/firewalletbrowser/branches")
@@ -1265,6 +1276,12 @@ def login_post():
account = request.form.get("account")
password = request.form.get("password")
if account == None or password == None:
wallets = account_module.listWallets()
wallets = render.wallets(wallets)
return render_template("login.html",
error="Invalid account or password",wallets=wallets)
# Check if the account is valid
if account.count(":") > 0:
wallets = account_module.listWallets()
@@ -1280,8 +1297,6 @@ def login_post():
wallets = render.wallets(wallets)
return render_template("login.html",
error="Invalid account or password",wallets=wallets)
# Set the cookie
response = make_response(redirect("/"))
response.set_cookie("account", account)
@@ -1300,6 +1315,11 @@ def register():
password = request.form.get("password")
repeatPassword = request.form.get("password_repeat")
if account == None or password == None or repeatPassword == None:
return render_template("register.html",
error="Invalid account or password",
name=account,password=password,password_repeat=repeatPassword)
# Check if the passwords match
if password != repeatPassword:
return render_template("register.html",
@@ -1329,10 +1349,8 @@ def register():
# Set the cookie
response = make_response(render_template("message.html",
title="Account Created",
content="Your account has been created. Here is your seed phrase. Please write it down and keep it safe as it will not be shown again<br><br>" + response['seed']))
response = make_response(render_template("message.html",title="Account Created",
content=f"Your account has been created. Here is your seed phrase. Please write it down and keep it safe as it will not be shown again<br><br>{response['seed']}"))
response.set_cookie("account", account+":"+password)
return response
@@ -1344,6 +1362,12 @@ def import_wallet():
repeatPassword = request.form.get("password_repeat")
seed = request.form.get("seed")
if account == None or password == None or repeatPassword == None or seed == None:
return render_template("import-wallet.html",
error="Invalid account, password or seed",
name=account,password=password,password_repeat=repeatPassword,
seed=seed)
# Check if the passwords match
if password != repeatPassword:
return render_template("import-wallet.html",
@@ -1560,6 +1584,7 @@ def api_hsd(function):
stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
state = domainInfo['info']['state']
next_action = ""
next = ""
if state == 'CLOSED':
if not domainInfo['info']['registered']:
if account_module.isOwnDomain(account,domain):
@@ -1638,7 +1663,10 @@ def api_wallet(function):
return jsonify({"error": "Not logged in"})
account = account_module.check_account(request.cookies.get("account"))
password = request.cookies.get("account").split(":")[1]
if not account:
return jsonify({"error": "Invalid account"})
password = request.cookies.get("account","").split(":")[1]
if not account:
return jsonify({"error": "Invalid account"})
@@ -1672,7 +1700,7 @@ def api_wallet(function):
if function == "domains":
domains = account_module.getDomains(account)
if 'error' in domains:
if type(domains) == dict and 'error' in domains:
return jsonify({"result": [], "error": domains['error']})
# Add nameRender to each domain
@@ -1683,7 +1711,7 @@ def api_wallet(function):
if function == "transactions":
# Get the page parameter
page = request.args.get('page')
page = request.args.get('page', 1)
try:
page = int(page)
except:
@@ -1759,7 +1787,7 @@ def api_wallet_mobile(function):
return jsonify({"error": "Not logged in"})
account = account_module.check_account(request.cookies.get("account"))
password = request.cookies.get("account").split(":")[1]
password = request.cookies.get("account","").split(":")[1]
if not account:
return jsonify({"error": "Invalid account"})
@@ -1821,7 +1849,11 @@ def renderDomain(name: str) -> str:
#region Assets and default pages
@app.route('/qr/<data>')
def qr(data):
return send_file(qrcode(data, mode="raw"), mimetype="image/png")
output = qrcode(data, mode="raw")
if output is None:
return jsonify({"error": "Invalid data"}), 400
return send_file(output, mimetype="image/png")
# Theme
@app.route('/assets/css/styles.min.css')

View File

@@ -148,11 +148,14 @@ def getPluginData(pluginStr: str):
def getPluginFunctions(plugin: str):
plugin = import_module(plugin.replace("/","."))
return plugin.functions
imported_plugin = import_module(plugin.replace("/","."))
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("/","."))
if function not in plugin_module.functions:
return {"error": "Function not found"}
@@ -189,13 +192,13 @@ def runPluginFunction(plugin: str, function: str, params: dict, authentication:
def getPluginFunctionInputs(plugin: str, function: str):
plugin = import_module(plugin.replace("/","."))
return plugin.functions[function]["params"]
imported_plugin = import_module(plugin.replace("/","."))
return imported_plugin.functions[function]["params"]
def getPluginFunctionReturns(plugin: str, function: str):
plugin = import_module(plugin.replace("/","."))
return plugin.functions[function]["returns"]
imported_plugin = import_module(plugin.replace("/","."))
return imported_plugin.functions[function]["returns"]
def getDomainFunctions():

View File

@@ -7,10 +7,8 @@ import os
from handywrapper import api
import threading
HSD_API = os.getenv("HSD_API")
HSD_IP = os.getenv("HSD_IP")
if HSD_IP is None:
HSD_IP = "localhost"
HSD_API = os.getenv("HSD_API","")
HSD_IP = os.getenv("HSD_IP","localhost")
HSD_NETWORK = os.getenv("HSD_NETWORK")
HSD_WALLET_PORT = 12039
@@ -96,6 +94,7 @@ actionMap = {
"UPDATE": "Updated ",
"REGISTER": "Registered ",
"RENEW": "Renewed ",
"OPEN": "Opened ",
"BID": "Bid on ",
"REVEAL": "Revealed bid for ",
"REDEEM": "Redeemed bid for ",
@@ -107,6 +106,7 @@ actionMapPlural = {
"UPDATE": "Updated multiple domains' records",
"REGISTER": "Registered multiple domains",
"RENEW": "Renewed multiple domains",
"OPEN": "Opened multiple domains",
"BID": "Bid on multiple domains",
"REVEAL": "Revealed multiple bids",
"REDEEM": "Redeemed multiple bids",
@@ -558,7 +558,6 @@ def renderDomainAsync(namehash: str) -> None:
if namehash in cache:
return
# Fetch the name outside the lock (network call)
name = hsd.rpc_getNameByHash(namehash)
if name["error"] is None:
@@ -574,7 +573,7 @@ def renderDomainAsync(namehash: str) -> None:
with open(NAMEHASH_CACHE, 'w') as f:
json.dump(cache, f)
return rendered
return
else:
print(f"Error fetching name for hash {namehash}: {name['error']}", flush=True)

View File

@@ -17,8 +17,8 @@ def gunicornServer():
def load_config(self):
for key, value in self.options.items():
if key in self.cfg.settings and value is not None:
self.cfg.set(key.lower(), value)
if key in self.cfg.settings and value is not None: # type: ignore
self.cfg.set(key.lower(), value) # type: ignore
def load(self):
return self.application