feat: Add send page

This commit is contained in:
Nathan Woodburn 2023-12-28 16:04:45 +11:00
parent f421f4f692
commit c0b407a6a2
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
13 changed files with 629 additions and 61 deletions

View File

@ -2,6 +2,9 @@ from handywrapper import api
import os
import dotenv
import requests
import re
import domainLookup
import json
dotenv.load_dotenv()
@ -16,6 +19,9 @@ response = hsd.getInfo()
def check_account(cookie: str):
if cookie is None:
return False
# Check the account
if cookie.count(":") < 1:
return False
@ -82,3 +88,84 @@ def getTransactions(account):
if 'error' in info:
return []
return info
def check_address(address: str, allow_name: bool = True, return_address: bool = False):
# Check if the address is valid
if address.startswith('@'):
# Check if the address is a name
if not allow_name and not return_address:
return 'Invalid address'
elif not allow_name and return_address:
return False
return check_hip2(address[1:])
# Check if the address is a valid HNS address
response = requests.post(f"http://x:{APIKEY}@127.0.0.1:12037",json={
"method": "validateaddress",
"params": [address]
}).json()
if response['error'] is not None:
if return_address:
return False
return 'Invalid address'
if response['result']['isvalid'] == True:
if return_address:
return address
return 'Valid address'
if return_address:
return False
return 'Invalid address'
def check_hip2(domain: str):
# Check if the domain is valid
domain = domain.lower()
if re.match(r'^[a-zA-Z0-9\-\.]{1,63}$', domain) is None:
return 'Invalid address'
address = domainLookup.hip2(domain)
if not check_address(address, False,True):
return 'Hip2: Lookup succeeded but address is invalid'
return address
def send(account,address,amount):
account_name = check_account(account)
password = ":".join(account.split(":")[1:])
# Unlock the account
response = requests.post(f"http://x:{APIKEY}@127.0.0.1:12039/wallet/{account_name}/unlock",
json={"passphrase": password,"timeout": 10})
if response.status_code != 200:
return {
"error": "Failed to unlock account"
}
if 'success' not in response.json():
return {
"error": "Failed to unlock account"
}
# Send the transaction
response = requests.post(f"http://x:{APIKEY}@127.0.0.1:12039",json={
"method": "sendtoaddress",
"params": [address,amount]
})
if response.status_code != 200:
return {
"error": "Failed to send transaction"
}
response = response.json()
if 'error' in response:
return {
"error": json.dumps(response['error'])
}
return {
"tx": response['result']
}

136
domainLookup.py Normal file
View File

@ -0,0 +1,136 @@
import dns.resolver
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import tempfile
import subprocess
import binascii
import datetime
import dns.asyncresolver
import httpx
from requests_doh import DNSOverHTTPSSession, add_dns_provider, remove_dns_provider
def hip2(domain: str):
domain_check = False
try:
# Get the IP
ip = resolve_with_doh(domain)
# Run the openssl s_client command
s_client_command = ["openssl","s_client","-showcerts","-connect",f"{ip}:443","-servername",domain,]
s_client_process = subprocess.Popen(s_client_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
s_client_output, _ = s_client_process.communicate(input=b"\n")
certificates = []
current_cert = ""
for line in s_client_output.split(b"\n"):
current_cert += line.decode("utf-8") + "\n"
if "-----END CERTIFICATE-----" in line.decode("utf-8"):
certificates.append(current_cert)
current_cert = ""
# Remove anything before -----BEGIN CERTIFICATE-----
certificates = [cert[cert.find("-----BEGIN CERTIFICATE-----"):] for cert in certificates]
if certificates:
cert = certificates[0]
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_cert_file:
temp_cert_file.write(cert)
temp_cert_file.seek(0) # Move back to the beginning of the temporary file
tlsa_command = ["openssl","x509","-in",temp_cert_file.name,"-pubkey","-noout","|","openssl","pkey","-pubin","-outform","der","|","openssl","dgst","-sha256","-binary",]
tlsa_process = subprocess.Popen(" ".join(tlsa_command), shell=True, stdout=subprocess.PIPE)
tlsa_output, _ = tlsa_process.communicate()
tlsa_server = "3 1 1 " + binascii.hexlify(tlsa_output).decode("utf-8")
# Get domains
cert_obj = x509.load_pem_x509_certificate(cert.encode("utf-8"), default_backend())
domains = []
for ext in cert_obj.extensions:
if ext.oid == x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
san_list = ext.value.get_values_for_type(x509.DNSName)
domains.extend(san_list)
# Extract the common name (CN) from the subject
common_name = cert_obj.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
if common_name:
if common_name[0].value not in domains:
domains.append(common_name[0].value)
if domains:
if domain in domains:
domain_check = True
else:
# Check if matching wildcard domain exists
for d in domains:
if d.startswith("*"):
if domain.split(".")[1:] == d.split(".")[1:]:
domain_check = True
break
expiry_date = cert_obj.not_valid_after
# Check if expiry date is past
if expiry_date < datetime.datetime.now():
return "Hip2: Certificate is expired"
else:
return "Hip2: No certificate found"
try:
# Check for TLSA record
tlsa = resolve_TLSA_with_doh(domain)
if not tlsa:
return "Hip2: TLSA lookup failed"
else:
if tlsa_server == str(tlsa):
if domain_check:
# Get the Hip2 addresss from /.well-known/wallets/HNS
add_dns_provider("HNSDoH", "https://hnsdoh.com/dns-query")
session = DNSOverHTTPSSession("HNSDoH")
r = session.get(f"https://{domain}/.well-known/wallets/HNS",verify=False)
return r.text
else:
return "Hip2: TLSA record matches certificate, but domain does not match certificate"
else:
return "Hip2: TLSA record does not match certificate"
except Exception as e:
return "Hip2: TLSA lookup failed with error: " + str(e)
# Catch all exceptions
except Exception as e:
return "Hip2: " + str(e)
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
return ip
def resolve_TLSA_with_doh(query_name, doh_url="https://hnsdoh.com/dns-query"):
query_name = "_443._tcp." + query_name
with httpx.Client() as client:
q = dns.message.make_query(query_name, dns.rdatatype.TLSA)
r = dns.query.https(q, doh_url, session=client)
tlsa = r.answer[0][0]
return tlsa

45
grant.md Normal file
View File

@ -0,0 +1,45 @@
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

106
main.py
View File

@ -4,6 +4,7 @@ import dotenv
import requests
import account as account_module
import render
import re
dotenv.load_dotenv()
@ -17,6 +18,9 @@ def index():
return redirect("/login")
account = account_module.check_account(request.cookies.get("account"))
if not account:
return redirect("/logout")
balance = account_module.getBalance(account)
available = balance['available']
total = balance['total']
@ -27,11 +31,13 @@ def index():
pending = account_module.getPendingTX(account)
domains = account_module.getDomains(account)
domain_count = len(domains)
domains = render.domains(domains)
return render_template("index.html", account=account, available=available,
total=total, pending=pending, domains=domains)
total=total, pending=pending, domains=domains, domain_count=domain_count)
@app.route('/tx')
@ -49,15 +55,93 @@ def transactions():
return render_template("tx.html", account=account, tx=transactions)
@app.route('/send')
def send_page():
# 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"))
max = account_module.getBalance(account)['available']
# Subtract approx fee of 0.02
max = max - 0.02
message = ''
address = ''
amount = ''
if 'message' in request.args:
message = request.args.get("message")
if 'address' in request.args:
address = request.args.get("address")
if 'amount' in request.args:
amount = request.args.get("amount")
return render_template("send.html", account=account,max=max,message=message,
address=address,amount=amount)
@app.route('/send', methods=["POST"])
def send():
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")
# Get the address and amount
address = request.form.get("address")
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)
address_check = account_module.check_address(address,True,True)
if not address_check:
return redirect("/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)
# Check if the amount is valid
amount = float(amount)
if amount <= 0:
return redirect("/send?message=Invalid amount&address=" + address + "&amount=" + str(amount))
if amount > account_module.getBalance(account)['available'] - 0.02:
return redirect("/send?message=Not enough funds to transfer&address=" + address + "&amount=" + str(amount))
# Send the transaction
response = account_module.send(request.cookies.get("account"),address,amount)
if 'error' in response:
return redirect("/send?message=" + response['error'] + "&address=" + address + "&amount=" + str(amount))
return redirect("/success?tx=" + response['tx'])
@app.route('/success')
def success():
# 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")
tx = request.args.get("tx")
return render_template("success.html", account=account, tx=tx)
@app.route('/checkaddress')
def check_address():
address = request.args.get("address")
if address is None:
return jsonify({"result": "Invalid address"})
return jsonify({"result": account_module.check_address(address)})
#region Account
@app.route('/login')
@ -65,9 +149,8 @@ def login():
if 'message' in request.args:
return render_template("login.html", error=request.args.get("message"))
accounts = account_module.getAccounts()
return render_template("login.html", accounts=accounts)
return render_template("login.html")
@app.route('/login', methods=["POST"])
def login_post():
@ -103,6 +186,15 @@ def logout():
def send_assets(path):
return send_from_directory('templates/assets', path)
# Try path
@app.route('/<path:path>')
def try_path(path):
if os.path.isfile("templates/" + path + ".html"):
return render_template(path + ".html")
else:
return page_not_found(404)
@app.errorhandler(404)
def page_not_found(e):
account = account_module.check_account(request.cookies.get("account"))

View File

@ -3,3 +3,6 @@ flask
python-dotenv
requests
gunicorn
dnspython
cryptography
requests-doh

View File

@ -5,13 +5,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Page Not Found - FireWallet</title>
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.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="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&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
@ -21,13 +19,17 @@
<div id="wrapper">
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="#">
<div class="sidebar-brand-icon rotate-n-15"><i class="fas fa-laugh-wink"></i></div>
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
</a>
<hr class="sidebar-divider my-0">
<ul class="navbar-nav text-light" id="accordionSidebar">
<li class="nav-item"><a class="nav-link" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
<li class="nav-item"><a class="nav-link" href="tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/send"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="margin-right: 4px;">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path>
</svg><span>Send HNS</span></a></li>
</ul>
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -5,13 +5,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Dashboard - FireWallet</title>
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.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="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&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
@ -21,13 +19,17 @@
<div id="wrapper">
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="#">
<div class="sidebar-brand-icon rotate-n-15"><i class="fas fa-laugh-wink"></i></div>
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
</a>
<hr class="sidebar-divider my-0">
<ul class="navbar-nav text-light" id="accordionSidebar">
<li class="nav-item"><a class="nav-link active" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
<li class="nav-item"><a class="nav-link" href="tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/send"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="margin-right: 4px;">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path>
</svg><span>Send HNS</span></a></li>
</ul>
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
</div>
@ -93,19 +95,13 @@
<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>Tasks</span></div>
<div class="text-uppercase text-info fw-bold text-xs mb-1"><span>Domains</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>50%</span></div>
</div>
<div class="col">
<div class="progress progress-sm">
<div class="progress-bar bg-info" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;"><span class="visually-hidden">50%</span></div>
</div>
<div class="text-dark fw-bold h5 mb-0 me-3"><span>{{domain_count}}</span></div>
</div>
</div>
</div>
<div class="col-auto"><i class="fas fa-clipboard-list fa-2x text-gray-300"></i></div>
</div>
</div>
</div>

View File

@ -5,13 +5,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Login - FireWallet</title>
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.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="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&amp;display=swap">
</head>

View File

@ -5,13 +5,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Register - FireWallet</title>
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.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="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&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">

127
templates/send.html Normal file
View File

@ -0,0 +1,127 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Send - 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&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
</head>
<body id="page-top">
<div id="wrapper">
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="#">
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
</a>
<hr class="sidebar-divider my-0">
<ul class="navbar-nav text-light" id="accordionSidebar">
<li class="nav-item"><a class="nav-link" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/send"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="margin-right: 4px;">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path>
</svg><span>Send HNS</span></a></li>
</ul>
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
</div>
</nav>
<div class="d-flex flex-column" id="content-wrapper">
<div id="content">
<nav class="navbar navbar-expand bg-white shadow mb-4 topbar static-top navbar-light">
<div class="container-fluid"><button class="btn btn-link d-md-none rounded-circle me-3" id="sidebarToggleTop" type="button"><i class="fas fa-bars"></i></button>
<form class="d-none d-sm-inline-block me-auto ms-md-3 my-2 my-md-0 mw-100 navbar-search">
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for domain"><button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button></div>
</form>
<ul class="navbar-nav flex-nowrap ms-auto">
<li class="nav-item dropdown d-sm-none no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><i class="fas fa-search"></i></a>
<div class="dropdown-menu dropdown-menu-end p-3 animated--grow-in" aria-labelledby="searchDropdown">
<form class="me-auto navbar-search w-100">
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for ...">
<div class="input-group-append"><button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button></div>
</div>
</form>
</div>
</li>
<li class="nav-item dropdown no-arrow">
<div class="nav-item dropdown no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><span class="d-none d-lg-inline me-2 text-gray-600 small">{{account}}</span><img class="border rounded-circle img-profile" src="/assets/img/HNS.png"></a>
<div class="dropdown-menu shadow dropdown-menu-end animated--grow-in"><a class="dropdown-item" href="/logout"><i class="fas fa-sign-out-alt fa-sm fa-fw me-2 text-gray-400"></i>&nbsp;Logout</a></div>
</div>
</li>
</ul>
</div>
</nav>
<div class="container-fluid text-center">
<h3 class="text-center text-dark mb-1">Send HNS</h3><span style="color: rgb(255,0,0);">{{message}}</span>
</div>
<form style="margin: auto;max-width: 400px;margin-top: 50px;" method="post">
<div style="margin-top: 25px;"><label class="form-label">Send to</label><input class="form-control" type="text" id="address" placeholder="Address or @domain" name="address" value="{{address}}"><span id="addressValid"></span><script>
function checkAddress(inputValue) {
// Make API request to "/checkaddress"
var apiUrl = '/checkaddress?address=' + encodeURIComponent(inputValue);
fetch(apiUrl)
.then(response => response.json())
.then(data => {
// Update the content of the span with the response data
var addressCheckSpan = document.getElementById('addressValid');
addressCheckSpan.textContent = data.result; // You can replace 'addressInfo' with the actual property you receive from the API
})
.catch(error => {
console.error('Error fetching data:', error);
});
}
// Function to handle input field blur event
function handleBlur() {
var inputField = document.getElementById('address');
var inputValue = inputField.value;
// Check if the input value is not empty
if (inputValue.trim() !== '') {
checkAddress(inputValue);
} else {
var addressCheckSpan = document.getElementById('addressValid');
addressCheckSpan.textContent = 'Invalid address';
}
}
// Add a blur event listener to the input field
var inputField = document.getElementById('address');
inputField.addEventListener('blur', handleBlur);
</script></div>
<div style="margin-top: 25px;"><label class="form-label">Amount</label><input class="form-control" type="text" id="amount" placeholder="0.00000" name="amount" style="margin-bottom: 10px;" value="{{amount}}"><span>Available to send: {{max}} HNS</span><button class="btn btn-primary btn-sm" id="maxButton" type="button" style="margin-left: 25px;">Send Max</button><script>
// Function to fill in the form input
function fillInput() {
// Get the input field and set its value
var inputField = document.getElementById('amount');
inputField.value = '{{max}}';
}
// Add a click event listener to the button
var fillButton = document.getElementById('maxButton');
fillButton.addEventListener('click', fillInput);
</script></div>
<div class="text-center" style="margin-top: 25px;"><input class="btn btn-primary" type="submit" value="Send"></div>
</form>
</div>
<footer class="bg-white sticky-footer">
<div class="container my-auto">
<div class="text-center my-auto copyright"><span>Copyright © FireWallet 2023</span></div>
</div>
</footer>
</div><a class="border rounded d-inline scroll-to-top" href="#page-top"><i class="fas fa-angle-up"></i></a>
</div>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/script.min.js"></script>
</body>
</html>

82
templates/success.html Normal file
View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Success - 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&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
</head>
<body id="page-top">
<div id="wrapper">
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="#">
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
</a>
<hr class="sidebar-divider my-0">
<ul class="navbar-nav text-light" id="accordionSidebar">
<li class="nav-item"><a class="nav-link" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/send"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="margin-right: 4px;">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path>
</svg><span>Send HNS</span></a></li>
</ul>
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
</div>
</nav>
<div class="d-flex flex-column" id="content-wrapper">
<div id="content">
<nav class="navbar navbar-expand bg-white shadow mb-4 topbar static-top navbar-light">
<div class="container-fluid"><button class="btn btn-link d-md-none rounded-circle me-3" id="sidebarToggleTop" type="button"><i class="fas fa-bars"></i></button>
<form class="d-none d-sm-inline-block me-auto ms-md-3 my-2 my-md-0 mw-100 navbar-search">
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for domain"><button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button></div>
</form>
<ul class="navbar-nav flex-nowrap ms-auto">
<li class="nav-item dropdown d-sm-none no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><i class="fas fa-search"></i></a>
<div class="dropdown-menu dropdown-menu-end p-3 animated--grow-in" aria-labelledby="searchDropdown">
<form class="me-auto navbar-search w-100">
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for ...">
<div class="input-group-append"><button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button></div>
</div>
</form>
</div>
</li>
<li class="nav-item dropdown no-arrow">
<div class="nav-item dropdown no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><span class="d-none d-lg-inline me-2 text-gray-600 small">{{account}}</span><img class="border rounded-circle img-profile" src="/assets/img/HNS.png"></a>
<div class="dropdown-menu shadow dropdown-menu-end animated--grow-in"><a class="dropdown-item" href="/logout"><i class="fas fa-sign-out-alt fa-sm fa-fw me-2 text-gray-400"></i>&nbsp;Logout</a></div>
</div>
</li>
</ul>
</div>
</nav>
<div class="container-fluid">
<h3 class="text-center text-dark mb-1">Transaction send successfully</h3>
</div>
<div class="card" style="max-width: 500px;margin: auto;margin-top: 50px;">
<div class="card-body">
<h4 class="card-title">Your transaction has been sent and will be mined soon.</h4><span style="display: block;font-size: 12px;">TX: {{tx}}</span><span style="display: block;">Check your transaction on a block explorer</span><a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a><a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a>
</div>
</div>
</div>
<footer class="bg-white sticky-footer">
<div class="container my-auto">
<div class="text-center my-auto copyright"><span>Copyright © FireWallet 2023</span></div>
</div>
</footer>
</div><a class="border rounded d-inline scroll-to-top" href="#page-top"><i class="fas fa-angle-up"></i></a>
</div>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/script.min.js"></script>
</body>
</html>

View File

@ -5,13 +5,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Transactions - FireWallet</title>
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.png">
<link rel="icon" type="image/png" sizes="670x700" href="/assets/img/HNS.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="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&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
@ -21,13 +19,17 @@
<div id="wrapper">
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="#">
<div class="sidebar-brand-icon rotate-n-15"><i class="fas fa-laugh-wink"></i></div>
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
</a>
<hr class="sidebar-divider my-0">
<ul class="navbar-nav text-light" id="accordionSidebar">
<li class="nav-item"><a class="nav-link" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
<li class="nav-item"><a class="nav-link" href="tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
<li class="nav-item"><a class="nav-link" href="/send"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="margin-right: 4px;">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path>
</svg><span>Send HNS</span></a></li>
</ul>
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
</div>