diff --git a/.gitignore b/.gitignore index 01a374d..5789dc0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .env - +.env* __pycache__/ templates/assets/css/styles.min.css @@ -12,4 +12,5 @@ plugins/signatures.json .venv/ user_data/ -customPlugins/ \ No newline at end of file +customPlugins/ +cache/ \ No newline at end of file diff --git a/FireWalletBrowser.bsdesign b/FireWalletBrowser.bsdesign index 44990d5..560ac79 100644 Binary files a/FireWalletBrowser.bsdesign and b/FireWalletBrowser.bsdesign differ diff --git a/README.md b/README.md index 23bd2ac..fd3df84 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # FireWalletBrowser ## Installation +See [here](https://firewallet.au/setup) for instructions on how to setup a FireWallet + ```bash git clone https://github.com/Nathanwoodburn/firewalletbrowser.git cd firewalletbrowser @@ -35,13 +37,13 @@ Also available as a docker image: To run using a HSD running directly on the host: ```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 ```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) @@ -115,11 +117,13 @@ Auction page ## Environment variables ```yaml -hsd_api: HSD API key -hsd_ip: HSD IP address -theme: Theme to use (dark-purple, black) -show_expired: Show expired domains (true/false) -exclude: Comma separated list of wallets to exclude from the wallet list +HSD_API: HSD API key +HSD_IP: HSD IP address +THEME: Theme to use (dark-purple, black) +SHOW_EXPIRED: Show expired domains (true/false) +EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary) +EXPLORER_TX: URL for exploring transactions (default https://niami.io/tx/) +HSD_NETWORK: Network to connect to (main, regtest, simnet) ``` diff --git a/account.py b/account.py index 359c561..1562fe4 100644 --- a/account.py +++ b/account.py @@ -6,29 +6,64 @@ import requests import re import domainLookup import json - +import time dotenv.load_dotenv() -APIKEY = os.getenv("hsd_api") -ip = os.getenv("hsd_ip") -if ip is None: - ip = "localhost" +HSD_API = os.getenv("HSD_API") +HSD_IP = os.getenv("HSD_IP") +if HSD_IP is None: + HSD_IP = "localhost" -show_expired = os.getenv("show_expired") -if show_expired is None: - show_expired = False +HSD_NETWORK = os.getenv("HSD_NETWORK") +HSD_WALLET_PORT = 12039 +HSD_NODE_PORT = 12037 -hsd = api.hsd(APIKEY,ip) -hsw = api.hsw(APIKEY,ip) +if not HSD_NETWORK: + HSD_NETWORK = "main" +else: + HSD_NETWORK = HSD_NETWORK.lower() + +if HSD_NETWORK == "simnet": + HSD_WALLET_PORT = 15039 + HSD_NODE_PORT = 15037 +elif HSD_NETWORK == "testnet": + HSD_WALLET_PORT = 13039 + HSD_NODE_PORT = 13037 +elif HSD_NETWORK == "regtest": + HSD_WALLET_PORT = 14039 + HSD_NODE_PORT = 14037 +SHOW_EXPIRED = os.getenv("SHOW_EXPIRED") +if SHOW_EXPIRED is None: + SHOW_EXPIRED = False + +hsd = api.hsd(HSD_API,HSD_IP,HSD_NODE_PORT) +hsw = api.hsw(HSD_API,HSD_IP,HSD_WALLET_PORT) + +cacheTime = 3600 + # Verify the connection response = hsd.getInfo() -exclude = ["primary"] -if os.getenv("exclude") is not None: - exclude = os.getenv("exclude").split(",") +EXCLUDE = ["primary"] +if os.getenv("EXCLUDE") is not None: + EXCLUDE = os.getenv("EXCLUDE").split(",") + +def hsdConnected(): + if hsdVersion() == -1: + return False + return True + +def hsdVersion(format=True): + info = hsd.getInfo() + if 'error' in info: + return -1 + if format: + return float('.'.join(info['version'].split(".")[:2])) + else: + return info['version'] def check_account(cookie: str): if cookie is None: @@ -61,12 +96,15 @@ def check_password(cookie: str, password: str): return True def createWallet(account: str, password: str): + if not hsdConnected(): + return { + "error": { + "message": "Node not connected" + } + } # Create the account # Python wrapper doesn't support this yet - response = requests.put(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}") - print(response) - print(response.json()) - + response = requests.put(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}") if response.status_code != 200: return { "error": { @@ -80,9 +118,8 @@ def createWallet(account: str, password: str): # Encrypt the wallet (python wrapper doesn't support this yet) - response = requests.post(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}/passphrase", + response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/passphrase", json={"passphrase": password}) - print(response) return { "seed": seed, @@ -91,16 +128,20 @@ def createWallet(account: str, password: str): } def importWallet(account: str, password: str,seed: str): + if not hsdConnected(): + return { + "error": { + "message": "Node not connected" + } + } + # Import the wallet data = { "passphrase": password, "mnemonic": seed, } - response = requests.put(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}",json=data) - print(response) - print(response.json()) - + response = requests.put(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}",json=data) if response.status_code != 200: return { "error": { @@ -122,11 +163,21 @@ def listWallets(): # Check if response is json or an array if isinstance(response, list): # 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 ['Wallet not connected'] +def selectWallet(account: str): + # Select wallet + response = hsw.rpc_selectWallet(account) + if response['error'] is not None: + return { + "error": { + "message": response['error']['message'] + } + } + def getBalance(account: str): # Get the total balance info = hsw.getBalance('default',account) @@ -171,25 +222,29 @@ def getAddress(account: str): return info['receiveAddress'] def getPendingTX(account: str): - # Get the pending transactions - info = hsw.getWalletTxHistory(account) - if 'error' in info: - return 0 pending = 0 - for tx in info: - if tx['confirmations'] < 1: - pending += 1 - + page = 1 + pageSize = 10 + while True: + txs = getTransactions(account,page,pageSize) + page+=1 + pendingPage = 0 + for tx in txs: + if tx['confirmations'] < 1: + pending+=1 + pendingPage+=1 + if pendingPage < pageSize: + break return pending def getDomains(account,own=True): if own: - response = requests.get(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}/name?own=true") + response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name?own=true") else: - response = requests.get(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}/name") + response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name") info = response.json() - if show_expired: + if SHOW_EXPIRED: return info # Remove any expired domains @@ -204,13 +259,99 @@ def getDomains(account,own=True): return domains -def getTransactions(account): - # Get the transactions - info = hsw.getWalletTxHistory(account) - if 'error' in info: - return [] - return info +def getPageTXCache(account,page,size=100): + page = f"{page}-{size}" + if not os.path.exists(f'cache'): + os.mkdir(f'cache') + if not os.path.exists(f'cache/{account}_page.json'): + with open(f'cache/{account}_page.json', 'w') as f: + f.write('{}') + with open(f'cache/{account}_page.json') as f: + pageCache = json.load(f) + + if page in pageCache and pageCache[page]['time'] > int(time.time()) - cacheTime: + return pageCache[page]['txid'] + return None + +def pushPageTXCache(account,page,txid,size=100): + page = f"{page}-{size}" + if not os.path.exists(f'cache/{account}_page.json'): + with open(f'cache/{account}_page.json', 'w') as f: + f.write('{}') + with open(f'cache/{account}_page.json') as f: + pageCache = json.load(f) + + pageCache[page] = { + 'time': int(time.time()), + 'txid': txid + } + with open(f'cache/{account}_page.json', 'w') as f: + json.dump(pageCache, f,indent=4) + + return pageCache[page]['txid'] + +def getTXFromPage(account,page,size=100): + if page == 1: + return getTransactions(account,1,size)[-1]['hash'] + + cached = getPageTXCache(account,page,size) + if cached: + return getPageTXCache(account,page,size) + previous = getTransactions(account,page,size) + if len(previous) == 0: + return None + hash = previous[-1]['hash'] + pushPageTXCache(account,page,hash,size) + return hash + + + +def getTransactions(account,page=1,limit=100): + # Get the transactions + if hsdVersion() < 7: + if page != 1: + return [] + info = hsw.getWalletTxHistory(account) + if 'error' in info: + return [] + return info[::-1] + + lastTX = None + if page < 1: + return [] + if page > 1: + lastTX = getTXFromPage(account,page-1,limit) + + if lastTX: + response = requests.get(f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}') + elif page == 1: + response = requests.get(f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}') + else: + return [] + + if response.status_code != 200: + print(response.text) + return [] + data = response.json() + + # Refresh the cache if the next page is different + nextPage = getPageTXCache(account,page,limit) + if nextPage is not None and nextPage != data[-1]['hash']: + print(f'Refreshing page {page}') + pushPageTXCache(account,page,data[-1]['hash'],limit) + return data + +def getAllTransactions(account): + # Get the transactions + page = 0 + txs = [] + while True: + txs += getTransactions(account,page,1000) + if len(txs) == 0: + break + page += 1 + return txs def check_address(address: str, allow_name: bool = True, return_address: bool = False): # Check if the address is valid @@ -223,7 +364,7 @@ def check_address(address: str, allow_name: bool = True, return_address: bool = return check_hip2(address[1:]) # Check if the address is a valid HNS address - response = requests.post(f"http://x:{APIKEY}@{ip}:12037",json={ + response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}",json={ "method": "validateaddress", "params": [address] }).json() @@ -272,7 +413,7 @@ def send(account,address,amount): response = hsw.rpc_walletPassphrase(password,10) # Unlock the account - # response = requests.post(f"http://x:{APIKEY}@{ip}:12039/wallet/{account_name}/unlock", + # response = requests.post(f"http://x:{APIKEY}@{ip}:{HSD_WALLET_PORT}/wallet/{account_name}/unlock", # json={"passphrase": password,"timeout": 10}) if response['error'] is not None: return { @@ -292,6 +433,15 @@ def send(account,address,amount): "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): # Get the domain response = hsd.rpc_getNameInfo(domain) @@ -328,11 +478,13 @@ def getDNS(domain: str): return { "error": "No DNS records" } + if response['result'] == None: + return [] + if 'records' not in response['result']: return [] return response['result']['records'] - def setDNS(account,domain,records): account_name = check_account(account) password = ":".join(account.split(":")[1:]) @@ -377,6 +529,9 @@ def setDNS(account,domain,records): response = hsw.sendUPDATE(account_name,password,domain,data) return response +def register(account,domain): + # Maybe add default dns records? + return setDNS(account,domain,'[]') def getNodeSync(): response = hsd.getInfo() @@ -422,12 +577,56 @@ def getBids(account, domain="NONE"): def getReveals(account,domain): 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): prevout = reveal['prevout'] hash = prevout['hash'] index = prevout['index'] tx = hsd.getTxByHash(hash) + if 'inputs' not in tx: + print(f'Something is up with this tx: {hash}') + print(tx) + print('---') + # No idea what happened here + # Check if registered? + return None return tx['inputs'][index]['prevout']['hash'] @@ -469,9 +668,8 @@ def revealAll(account): response = hsw.rpc_walletPassphrase(password,10) if response['error'] is not None: return - # Try to send the batch of all renew, reveal and redeem actions - return requests.post(f"http://x:{APIKEY}@{ip}:12039",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: return { "error": { @@ -479,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): # Get height of the start of the auction response = hsw.rpc_selectWallet(account) @@ -696,7 +946,7 @@ def sendBatch(account, batch): "message": response['error']['message'] } } - response = requests.post(f"http://x:{APIKEY}@{ip}:12039",json={ + response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",json={ "method": "sendbatch", "params": [batch] }).json() @@ -756,7 +1006,7 @@ def zapTXs(account): } try: - response = requests.post(f"http://x:{APIKEY}@{ip}:12039/wallet/{account_name}/zap", + response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account_name}/zap", json={"age": age, "account": "default" }) diff --git a/example.env b/example.env index 96a6a46..6ceaef9 100644 --- a/example.env +++ b/example.env @@ -1,4 +1,5 @@ -hsd_api=123480615465636893475aCwyaae6s45 -hsd_ip=localhost -theme=black -show_expired=false \ No newline at end of file +HSD_API=123480615465636893475aCwyaae6s45 +HSD_IP=localhost +THEME=black +SHOW_EXPIRED=false +EXPLORER_TX=https://niami.io/tx/ \ No newline at end of file diff --git a/main.py b/main.py index e1e593b..d6977c7 100644 --- a/main.py +++ b/main.py @@ -26,7 +26,7 @@ fees = 0.02 revokeCheck = random.randint(100000,999999) -theme = os.getenv("theme") +THEME = os.getenv("THEME") @app.route('/') def index(): @@ -38,15 +38,6 @@ def index(): if not account: return redirect("/logout") - balance = account_module.getBalance(account) - available = balance['available'] - total = balance['total'] - - # Add commas to the numbers - available = "{:,}".format(available) - total = "{:,}".format(total) - - pending = account_module.getPendingTX(account) domains = account_module.getDomains(account) # Sort @@ -86,12 +77,7 @@ def index(): domains = sorted(domains, key=lambda k: k['name'],reverse=reverse) sort_domain = direction sort_domain_next = reverseDirection(direction) - - - - - domain_count = len(domains) domainsMobile = render.domains(domains,True) domains = render.domains(domains) @@ -101,11 +87,8 @@ def index(): functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{},request.cookies.get("account")) plugins += render.plugin_output_dash(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"])) - return render_template("index.html", account=account, available=available, - total=total, pending=pending, domains=domains, - domainsMobile=domainsMobile, plugins=plugins, - domain_count=domain_count, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("index.html", account=account,domains=domains, + domainsMobile=domainsMobile, plugins=plugins, sort_price=sort_price,sort_expiry=sort_expiry, sort_domain=sort_domain,sort_price_next=sort_price_next, sort_expiry_next=sort_expiry_next,sort_domain_next=sort_domain_next) @@ -125,14 +108,23 @@ def transactions(): return redirect("/login") account = account_module.check_account(request.cookies.get("account")) - # Get the transactions - transactions = account_module.getTransactions(account) + page = request.args.get('page') + try: + page = int(page) + except: + page = 1 + + if page < 1: + page = 1 + + transactions = account_module.getTransactions(account,page) + txCount = len(transactions) transactions = render.transactions(transactions) - - return render_template("tx.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(),tx=transactions) - + return render_template("tx.html", account=account, + tx=transactions, + page=page,txCount=txCount) + @app.route('/send') def send_page(): @@ -158,8 +150,8 @@ def send_page(): amount = request.args.get("amount") - return render_template("send.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("send.html", account=account, + max=max,message=message,address=address,amount=amount) @app.route('/send', methods=["POST"]) @@ -208,8 +200,8 @@ def send(): return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")), - sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(),action=action, + + action=action, content=content,cancel=cancel,confirm=confirm) @@ -238,8 +230,8 @@ def receive(): address = account_module.getAddress(account) - return render_template("receive.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("receive.html", account=account, + address=address) @app.route('/success') @@ -253,8 +245,8 @@ def success(): return redirect("/logout") tx = request.args.get("tx") - return render_template("success.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(),tx=tx) + return render_template("success.html", account=account, + tx=tx) @app.route('/checkaddress') def check_address(): @@ -276,14 +268,6 @@ def auctions(): if not account: return redirect("/logout") - balance = account_module.getBalance(account) - locked = balance['locked'] - # Round to 2 decimals - locked = round(locked, 2) - - # Add commas to the numbers - locked = "{:,}".format(locked) - bids = account_module.getBids(account) domains = account_module.getDomains(account,False) @@ -338,36 +322,24 @@ def auctions(): 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 = "" message = '' if 'message' in request.args: message = request.args.get("message") - return render_template("auctions.html", account=account, locked=locked, domains=bidsHtml, + return render_template("auctions.html", account=account, domains=bidsHtml, domainsMobile=bidsHtml, plugins=plugins, - domain_count=bidsHtml, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), - sort_price=sort_price,sort_state=sort_state, - sort_domain=sort_domain,sort_price_next=sort_price_next, + domain_count=bidsHtml,sort_price=sort_price, + sort_state=sort_state,sort_domain=sort_domain, + sort_price_next=sort_price_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) @app.route('/reveal') +@app.route('/all/reveal') def revealAllBids(): # Check if the user is logged in if request.cookies.get("account") is None: @@ -388,6 +360,46 @@ def revealAllBids(): 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') def search(): # Check if the user is logged in @@ -421,13 +433,13 @@ def search(): plugins += "" if 'error' in domain: - return render_template("search.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("search.html", account=account, + search_term=search_term, domain=domain['error'],plugins=plugins) if domain['info'] is None: - return render_template("search.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("search.html", account=account, + search_term=search_term,domain=search_term, state="AVAILABLE", next="Available Now",plugins=plugins) @@ -470,8 +482,8 @@ def search(): dns = render.dns(dns) txs = render.txs(txs) - return render_template("search.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("search.html", account=account, + search_term=search_term,domain=domain['info']['name'], raw=domain,state=state, next=next, owner=owner, dns=dns, txs=txs,plugins=plugins) @@ -496,8 +508,8 @@ def manage(domain: str): domain_info = account_module.getDomain(domain) if 'error' in domain_info: - return render_template("manage.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("manage.html", account=account, + domain=domain, error=domain_info['error']) expiry = domain_info['info']['stats']['daysUntilExpire'] @@ -533,8 +545,8 @@ def manage(domain: str): plugins += "" - return render_template("manage.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("manage.html", account=account, + error=errorMessage, address=address, domain=domain,expiry=expiry, dns=dns, raw_dns=urllib.parse.quote(raw_dns), @@ -603,8 +615,8 @@ def revokeInit(domain: str): return render_template("confirm-password.html", account=account_module.check_account(request.cookies.get("account")), - sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(),action=action, + + action=action, content=content,cancel=cancel,confirm=confirm,check=revokeCheck) @app.route('/manage//revoke/confirm', methods=["POST"]) @@ -710,8 +722,8 @@ def editPage(domain: str): errorMessage = "" - return render_template("edit.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("edit.html", account=account, + domain=domain, error=errorMessage, dns=dns,raw_dns=urllib.parse.quote(raw_dns)) @@ -770,8 +782,8 @@ def transfer(domain): return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")), - sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(),action=action, + + action=action, content=content,cancel=cancel,confirm=confirm) @app.route('/manage//sign') @@ -811,8 +823,8 @@ def signMessage(domain): - return render_template("message.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("message.html", account=account, + title="Sign Message",content=content) @@ -851,18 +863,27 @@ def auction(domain): return redirect("/") domainInfo = account_module.getDomain(search_term) + error = request.args.get("error") + if error == None: + error = "" if 'error' in domainInfo: - return render_template("auction.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), - search_term=search_term, domain=domainInfo['error']) + return render_template("auction.html", account=account, + + search_term=search_term, domain=domainInfo['error'], + error=error) if domainInfo['info'] is None: - next_action = f'Open Auction' - return render_template("auction.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + if domainInfo['registered'] == False and domainInfo['expired'] == False: + # Needs to be registered + next_action = f'ERROR GETTING NEXT STATE' + else: + next_action = f'Open Auction' + return render_template("auction.html", account=account, + search_term=search_term,domain=search_term,next_action=next_action, - state="AVAILABLE", next="Open Auction") + state="AVAILABLE", next="Open Auction", + error=error) state = domainInfo['info']['state'] next_action = '' @@ -883,9 +904,17 @@ def auction(domain): if state == 'CLOSED': if not domainInfo['info']['registered']: - state = 'AVAILABLE' - next = "Available Now" - next_action = f'Open Auction' + if account_module.isOwnDomain(account,domain): + print("Waiting to be registered") + state = 'PENDING REGISTER' + next = "Pending Register" + next_action = f'Register Domain' + + else: + print("Not registered") + state = 'AVAILABLE' + next = "Available Now" + next_action = f'Open Auction' else: state = 'REGISTERED' expires = domainInfo['info']['stats']['daysUntilExpire'] @@ -912,8 +941,8 @@ def auction(domain): message = request.args.get("message") - return render_template("auction.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("auction.html", account=account, + search_term=search_term,domain=domainInfo['info']['name'], raw=domainInfo,state=state, next=next, next_action=next_action, bids=bids,error=message) @@ -957,7 +986,7 @@ def bid(domain): blind = float(blind) if bid+blind == 0: - return redirect("/auction/" + domain+ "?message=Invalid bid amount") + return redirect("/auction/" + domain+ "?error=Invalid bid amount") # Show confirm page @@ -976,7 +1005,7 @@ def bid(domain): return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")), - sync=account_module.getNodeSync(),wallet_status=account_module.getWalletStatus(), + action=action, domain=domain,content=content,cancel=cancel,confirm=confirm) @@ -1009,7 +1038,7 @@ def bid_confirm(domain): float(blind)) print(response) if 'error' in response: - return redirect("/auction/" + domain + "?message=" + response['error']['message']) + return redirect("/auction/" + domain + "?error=" + response['error']['message']) return redirect("/success?tx=" + response['hash']) @@ -1028,7 +1057,7 @@ def open_auction(domain): if 'error' in response: if response['error'] != None: - return redirect("/auction/" + domain + "?message=" + response['error']['message']) + return redirect("/auction/" + domain + "?error=" + response['error']['message']) print(response) return redirect("/success?tx=" + response['hash']) @@ -1042,7 +1071,22 @@ def reveal_auction(domain): return redirect("/logout") 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//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: return redirect("/auction/" + domain + "?message=" + response['error']['message']) return redirect("/success?tx=" + response['hash']) @@ -1067,8 +1111,9 @@ def settings(): success = "" if not os.path.exists(".git"): - return render_template("settings.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("settings.html", account=account, + + hsd_version=account_module.hsdVersion(False), error=error,success=success,version="Error") info = gitinfo.get_git_info() branch = info['refs'] @@ -1081,8 +1126,9 @@ def settings(): last_commit = datetime.datetime.strptime(last_commit, "%Y-%m-%d %H:%M:%S") version = f'{last_commit.strftime("%y-%m-%d")} {branch}' - return render_template("settings.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("settings.html", account=account, + + hsd_version=account_module.hsdVersion(False), error=error,success=success,version=version) @app.route('/settings/') @@ -1119,8 +1165,8 @@ def settings_action(action): content += "" content += "" - return render_template("message.html", account=account,sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("message.html", account=account, + title="xPub Key", content=""+xpub+"" + content) @@ -1138,12 +1184,12 @@ def login(): if 'message' in request.args: - return render_template("login.html", sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("login.html", + error=request.args.get("message"),wallets=wallets) - return render_template("login.html", sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("login.html", + wallets=wallets) @app.route('/login', methods=["POST"]) @@ -1156,8 +1202,8 @@ def login_post(): if account.count(":") > 0: wallets = account_module.listWallets() wallets = render.wallets(wallets) - return render_template("login.html", sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("login.html", + error="Invalid account",wallets=wallets) account = account + ":" + password @@ -1166,7 +1212,7 @@ def login_post(): if not account_module.check_password(account,password): wallets = account_module.listWallets() wallets = render.wallets(wallets) - return render_template("login.html", sync=account_module.getNodeSync(), + return render_template("login.html", error="Invalid account or password",wallets=wallets) @@ -1217,8 +1263,8 @@ def register(): # Set the cookie - response = make_response(render_template("message.html", sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + 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

" + response['seed'])) response.set_cookie("account", account+":"+password) @@ -1298,10 +1344,10 @@ def plugins_index(): if not account: return redirect("/logout") - plugins = render.plugins(plugins_module.listPlugins()) + plugins = render.plugins(plugins_module.listPlugins(True)) - return render_template("plugins.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("plugins.html", account=account, + plugins=plugins) @app.route('/plugin//') @@ -1333,8 +1379,8 @@ def plugin(ptype,plugin): if error == None: error = "" - return render_template("plugin.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("plugin.html", account=account, + name=data['name'],description=data['description'], author=data['author'],version=data['version'], source=data['source'],functions=functions,error=error) @@ -1405,14 +1451,76 @@ def plugin_function(ptype,plugin,function): return redirect("/plugin/" + plugin + "?error=" + response['error']) response = render.plugin_output(response,plugins_module.getPluginFunctionReturns(plugin,function)) - return render_template("plugin-output.html", account=account, sync=account_module.getNodeSync(), - wallet_status=account_module.getWalletStatus(), + return render_template("plugin-output.html", account=account, + name=data['name'],description=data['description'],output=response) else: return jsonify({"error": "Function not found"}) +#endregion + +#region API Routes +@app.route('/api/v1/hsd/', methods=["GET"]) +def api_hsd(function): + # Check if the user is logged in + if request.cookies.get("account") is None: + return jsonify({"error": "Not logged in"}) + + account = account_module.check_account(request.cookies.get("account")) + if not account: + return jsonify({"error": "Invalid account"}) + + if function == "sync": + return jsonify({"result": account_module.getNodeSync()}) + if function == "version": + return jsonify({"result": account_module.hsdVersion(False)}) + if function == "height": + return jsonify({"result": account_module.getBlockHeight()}) + + return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400 + +@app.route('/api/v1/wallet/', methods=["GET"]) +def api_wallet(function): + # Check if the user is logged in + if request.cookies.get("account") is None: + return jsonify({"error": "Not logged in"}) + + account = account_module.check_account(request.cookies.get("account")) + if not account: + return jsonify({"error": "Invalid account"}) + + if function == "sync": + return jsonify({"result": account_module.getWalletStatus()}) + + if function == "available": + return jsonify({"result": account_module.getBalance(account)['available']}) + if function == "total": + return jsonify({"result": account_module.getBalance(account)['total']}) + if function == "pending": + return jsonify({"result": account_module.getPendingTX(account)}) + if function == "locked": + return jsonify({"result": account_module.getBalance(account)['locked']}) + + if function == "domainCount": + 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 + + + #endregion @@ -1424,9 +1532,9 @@ def qr(data): # Theme @app.route('/assets/css/styles.min.css') def send_css(): - if theme == "live": + if THEME == "live": 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/') def send_assets(path): @@ -1435,6 +1543,12 @@ def send_assets(path): # Try path @app.route('/') def try_path(path): + # Check if node connected + if not account_module.hsdConnected(): + return redirect("/login?message=Node not connected") + + + if os.path.isfile("templates/" + path + ".html"): return render_template(path + ".html") else: diff --git a/plugin.py b/plugin.py index ced95b7..bc2dd9c 100644 --- a/plugin.py +++ b/plugin.py @@ -6,9 +6,8 @@ import hashlib import subprocess -def listPlugins(): +def listPlugins(update=False): plugins = [] - customPlugins = [] for file in os.listdir("plugins"): if file.endswith(".py"): if file != "main.py": @@ -36,9 +35,8 @@ def listPlugins(): if not os.path.exists(f"customPlugins/{importPath}"): if os.system(f"git clone {importurl} customPlugins/{importPath}") != 0: continue - else: - if os.system(f"cd customPlugins/{importPath} && git pull") != 0: - continue + elif update: + os.system(f"cd customPlugins/{importPath} && git pull") # Import plugins from customPlugins/ for file in os.listdir(f"customPlugins/{importPath}"): diff --git a/plugins/automations.py b/plugins/automations.py index 5fb17ec..b062015 100644 --- a/plugins/automations.py +++ b/plugins/automations.py @@ -5,10 +5,10 @@ import threading import os import time -APIKEY = os.environ.get("hsd_api") -ip = os.getenv("hsd_ip") -if ip is None: - ip = "localhost" +KEY = account.HSD_API +IP = account.HSD_IP +PORT = account.HSD_WALLET_PORT + if not os.path.exists("user_data"): os.mkdir("user_data") @@ -148,9 +148,9 @@ def automations_background(authentication): if response['error'] is not None: return # Try to send the batch of all renew, reveal and redeem actions - requests.post(f"http://x:{APIKEY}@{ip}:12039",json={"method": "sendbatch","params": [[["RENEW"]]]}) - requests.post(f"http://x:{APIKEY}@{ip}:12039",json={"method": "sendbatch","params": [[["REVEAL"]]]}) - requests.post(f"http://x:{APIKEY}@{ip}:12039",json={"method": "sendbatch","params": [[["REDEEM"]]]}) + requests.post(f"http://x:{KEY}@{IP}:{PORT}",json={"method": "sendbatch","params": [[["RENEW"]]]}) + requests.post(f"http://x:{KEY}@{IP}:{PORT}",json={"method": "sendbatch","params": [[["REVEAL"]]]}) + requests.post(f"http://x:{KEY}@{IP}:{PORT}",json={"method": "sendbatch","params": [[["REDEEM"]]]}) except Exception as e: print(e) diff --git a/plugins/batching.py b/plugins/batching.py index e1770b2..d5121c5 100644 --- a/plugins/batching.py +++ b/plugins/batching.py @@ -500,16 +500,15 @@ def advancedChangeLookahead(params, authentication): lookahead = int(lookahead) wallet = authentication.split(":")[0] password = ":".join(authentication.split(":")[1:]) - APIKEY = os.getenv("hsd_api") - ip = os.getenv("hsd_ip") - if ip is None: - ip = "localhost" + KEY = account.HSD_API + IP = account.HSD_IP + PORT = account.HSD_WALLET_PORT # Unlock wallet - response = requests.post(f"http://x:{APIKEY}@{ip}:12039/wallet/{wallet}/unlock", + response = requests.post(f"http://x:{KEY}@{IP}:{PORT}/wallet/{wallet}/unlock", json={"passphrase": password, "timeout": 10}) - response = requests.patch(f"http://x:{APIKEY}@{ip}:12039/wallet/{wallet}/account/default", + response = requests.patch(f"http://x:{KEY}@{IP}:{PORT}/wallet/{wallet}/account/default", json={"lookahead": lookahead}) diff --git a/plugins/renewal.py b/plugins/renewal.py index 9a9f850..9819e8b 100644 --- a/plugins/renewal.py +++ b/plugins/renewal.py @@ -50,12 +50,11 @@ def main(params, authentication): batches.append(names[i:i+100]) # Unlock wallet - api_key = os.getenv("hsd_api") - ip = os.getenv("hsd_ip") - if api_key is None: - print("API key not set") - return {"status": "API key not set", "transaction": "None"} - response = requests.post(f'http://x:{api_key}@{ip}:12039/wallet/{wallet}/unlock', + KEY = account.HSD_API + IP = account.HSD_IP + PORT = account.HSD_WALLET_PORT + + response = requests.post(f'http://x:{KEY}@{IP}:{PORT}/wallet/{wallet}/unlock', json={'passphrase': password, 'timeout': 600}) if response.status_code != 200: print("Failed to unlock wallet") @@ -74,7 +73,7 @@ def main(params, authentication): batchTX = "[" + ", ".join(batch) + "]" responseContent = f'{{"method": "sendbatch","params":[ {batchTX} ]}}' - response = requests.post(f'http://x:{api_key}@{ip}:12039', data=responseContent) + response = requests.post(f'http://x:{KEY}@{IP}:{PORT}', data=responseContent) if response.status_code != 200: print("Failed to create batch",flush=True) print(f'Status code: {response.status_code}',flush=True) diff --git a/plugins/txcount.py b/plugins/txcount.py index 7a1fcf8..c07579b 100644 --- a/plugins/txcount.py +++ b/plugins/txcount.py @@ -29,7 +29,14 @@ functions = { def main(params, authentication): wallet = authentication.split(":")[0] - txs = account.getTransactions(wallet) + txCount = 0 + page = 1 + while True: + txs = account.getTransactions(wallet,page) + if len(txs) == 0: + break + txCount += len(txs) + page += 1 - return {"txs": f'Total TXs: {len(txs)}'} + return {"txs": f'Total TXs: {txCount}'} \ No newline at end of file diff --git a/render.py b/render.py index 033e52c..acf00d8 100644 --- a/render.py +++ b/render.py @@ -3,6 +3,14 @@ import json import urllib.parse from flask import render_template 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): html = '' @@ -21,19 +29,25 @@ def domains(domains, mobile=False): if 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: - html += f'{name}{expires} days{paid} HNSManage' + html += f'{name}{expires} days{paid:,.2f} HNS{link_action}' else: - html += f'{name}{expires} days' + html += f'{name}{expires} days' return html def transactions(txs): + + if len(txs) == 0: + return 'No transactions found' html = '' - - # Reverse the list - txs = txs[::-1] - for tx in txs: action = "HNS Transfer" address = tx["outputs"][0]["address"] @@ -59,17 +73,15 @@ def transactions(txs): amount += output["value"] amount = amount / 1000000 - amount = round(amount, 2) - amount = "{:,}".format(amount) - hash = "" + hash[:8] + "..." + hash = f"{hash[:8]}..." if confirmations < 5: - confirmations = "" + str(confirmations) + "" + confirmations = f"{confirmations}" else: - confirmations = "" + str(confirmations) + "" + confirmations = f"{confirmations:,}" - html += f'{action}{address}{hash}{confirmations}{amount} HNS' + html += f'{action}{address}{hash}{confirmations}{amount:,.2f} HNS' return html @@ -93,14 +105,14 @@ def dns(data, edit=False): 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"{ds}\n" else: value = "" for key, val in entry.items(): if key != 'type': - value += str(val) + " " + value += f'{val} ' html_output += f"{value}\n" if edit: @@ -120,18 +132,16 @@ def txs(data): for entry in data: html_output += f"{entry['action']}\n" - html_output += f"{entry['txid'][:8]}...\n" + html_output += f"{entry['txid'][:8]}...\n" amount = entry['amount'] amount = amount / 1000000 - amount = round(amount, 2) if entry['blind'] == None: - html_output += f"{amount} HNS\n" + html_output += f"{amount:,.2f} HNS\n" else: blind = entry['blind'] blind = blind / 1000000 - blind = round(blind, 2) - html_output += f"{amount} + {blind} HNS\n" + html_output += f"{amount:,.2f} + {blind:,.2f} HNS\n" html_output += f"{timestamp_to_readable_time(entry['time'])}\n" html_output += f"\n" @@ -150,20 +160,17 @@ def bids(bids,reveals): for bid in bids: lockup = bid['lockup'] lockup = lockup / 1000000 - lockup = round(lockup, 2) html += "" - html += f"{lockup} HNS" + html += f"{lockup:,.2f} HNS" revealed = False for reveal in reveals: if reveal['bid'] == bid['prevout']['hash']: revealed = True value = reveal['value'] value = value / 1000000 - value = round(value, 2) - html += f"{value} HNS" + html += f"{value:,.2f} HNS" bidValue = lockup - value - bidValue = round(bidValue, 2) - html += f"{bidValue} HNS" + html += f"{bidValue:,.2f} HNS" break if not revealed: html += f"Hidden until reveal" @@ -184,22 +191,17 @@ def bidDomains(bids,domains, sortState=False): if bid['name'] == domain['name']: lockup = bid['lockup'] lockup = lockup / 1000000 - lockup = round(lockup, 2) bidValue = bid['value'] / 1000000 - bidValue = round(bidValue, 2) blind = lockup - bidValue - bidValue = "{:,}".format(bidValue) - blind = round(blind, 2) - blind = "{:,}".format(blind) - bidDisplay = f'{bidValue} HNS + {blind} HNS blind' + bidDisplay = f'{bidValue:,.2f} HNS + {blind:,.2f} HNS blind' html += "" - html += f"{domain['name']}" + html += f"{domain['name']}" html += f"{domain['state']}" html += f"{bidDisplay}" - html += f"{bid['height']}" + html += f"{bid['height']:,}" html += "" else: for domain in domains: @@ -207,18 +209,15 @@ def bidDomains(bids,domains, sortState=False): if bid['name'] == domain['name']: lockup = bid['lockup'] lockup = lockup / 1000000 - lockup = round(lockup, 2) bidValue = bid['value'] / 1000000 - bidValue = round(bidValue, 2) blind = lockup - bidValue - bidValue = "{:,}".format(bidValue) - blind = "{:,}".format(blind) - bidDisplay = f'{bidValue} HNS + {blind} HNS blind' + bidDisplay = f'{bidValue:,.2f} HNS + {blind:,.2f} HNS blind' html += "" - html += f"{domain['name']}" + html += f"{domain['name']}" html += f"{domain['state']}" html += f"{bidDisplay}" + html += f"{domain['height']:,}" html += "" return html diff --git a/templates/404.html b/templates/404.html index 9043c5e..1ec86d0 100644 --- a/templates/404.html +++ b/templates/404.html @@ -43,7 +43,7 @@
Sync: {{sync}}%Wallet: {{wallet_status}} + Sync: {{sync}}%Wallet: {{wallet_status}}Height: {{height}}
diff --git a/templates/assets/js/script.min.js b/templates/assets/js/script.min.js index 4521feb..a5f151a 100644 --- a/templates/assets/js/script.min.js +++ b/templates/assets/js/script.min.js @@ -1 +1 @@ -!function(){"use strict";var e=document.querySelector(".sidebar"),o=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var t=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var l of o)l.addEventListener("click",(function(o){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var l of t)l.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of t)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 o=e.originalEvent,t=o.wheelDelta||-o.detail;this.scrollTop+=30*(t<0?1:-1),e.preventDefault()}}));var i=document.querySelector(".scroll-to-top");i&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;i.style.display=e>100?"block":"none"}))}(); \ No newline at end of file +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","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked"];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"}))}(); \ No newline at end of file diff --git a/templates/auction.html b/templates/auction.html index 11cdf3c..b4aa25d 100644 --- a/templates/auction.html +++ b/templates/auction.html @@ -43,7 +43,7 @@
Sync: {{sync}}%Wallet: {{wallet_status}} + Sync: {{sync}}%Wallet: {{wallet_status}}Height: {{height}}
diff --git a/templates/auctions.html b/templates/auctions.html index 2491973..db8e5b9 100644 --- a/templates/auctions.html +++ b/templates/auctions.html @@ -43,7 +43,7 @@
Sync: {{sync}}%Wallet: {{wallet_status}} + Sync: {{sync}}%Wallet: {{wallet_status}}Height: {{height}}
+ + + + +
+
+
+
+
+
Pending Redeem
+
+
+
0
+
+ +
+
+
+
+
+
+
+
+
+
+
+
Pending Register
+
+
+
0
+
+
@@ -164,7 +198,7 @@
- +
diff --git a/templates/confirm-password.html b/templates/confirm-password.html index 0da1c89..cf5e0f9 100644 --- a/templates/confirm-password.html +++ b/templates/confirm-password.html @@ -43,7 +43,7 @@
Sync: {{sync}}%Wallet: {{wallet_status}} + Sync: {{sync}}%Wallet: {{wallet_status}}Height: {{height}}
diff --git a/templates/confirm.html b/templates/confirm.html index 9e842c3..54d65f9 100644 --- a/templates/confirm.html +++ b/templates/confirm.html @@ -43,7 +43,7 @@
Sync: {{sync}}%Wallet: {{wallet_status}} + Sync: {{sync}}%Wallet: {{wallet_status}}Height: {{height}}
diff --git a/templates/edit.html b/templates/edit.html index 3d4a511..8dc1bc4 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -123,7 +123,7 @@
- +
diff --git a/templates/index.html b/templates/index.html index 46ba406..351a264 100644 --- a/templates/index.html +++ b/templates/index.html @@ -43,7 +43,7 @@
Sync: {{sync}}%Wallet: {{wallet_status}} + Sync: {{sync}}%Wallet: {{wallet_status}}Height: {{height}}