From 85402651736bd60050abf6d14a6dac4569db728d Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Fri, 26 Jan 2024 03:51:52 +1100 Subject: [PATCH] feat: Finish domain transfers --- account.py | 194 ++++++++++++++++++++++++++++++-- main.py | 184 +++++++++++++++++++++++++++++- templates/confirm-password.html | 85 ++++++++++++++ templates/manage.html | 6 +- 4 files changed, 454 insertions(+), 15 deletions(-) create mode 100644 templates/confirm-password.html diff --git a/account.py b/account.py index 76fbc96..232ca60 100644 --- a/account.py +++ b/account.py @@ -1,3 +1,4 @@ +from datetime import datetime, timedelta from handywrapper import api import os import dotenv @@ -34,6 +35,20 @@ def check_account(cookie: str): return account +def check_password(cookie: str, password: str): + account = check_account(cookie) + if account == False: + return False + + # Check if the password is valid + info = hsw.rpc_selectWallet(account) + if info['error'] is not None: + return False + info = hsw.rpc_walletPassphrase(password,10) + if info['error'] is not None: + return False + return True + def getBalance(account: str): # Get the total balance @@ -54,6 +69,13 @@ def getBalance(account: str): return {'available': available, 'total': total} +def getBlockHeight(): + # Get the block height + info = hsd.getInfo() + if 'error' in info: + return 0 + return info['chain']['height'] + def getAddress(account: str): # Get the address info = hsw.getAccountInfo(account, 'default') @@ -149,7 +171,9 @@ def send(account,address,amount): response = hsw.rpc_selectWallet(account_name) if response['error'] is not None: return { - "error": response['error']['message'] + "error": { + "message": response['error']['message'] + } } response = hsw.rpc_walletPassphrase(password,10) @@ -158,13 +182,17 @@ def send(account,address,amount): # json={"passphrase": password,"timeout": 10}) if response['error'] is not None: return { - "error": response['error']['message'] + "error": { + "message": response['error']['message'] + } } response = hsw.rpc_sendToAddress(address,amount) if response['error'] is not None: return { - "error": response['error']['message'] + "error": { + "message": response['error']['message'] + } } return { "tx": response['result'] @@ -175,7 +203,9 @@ def getDomain(domain: str): response = hsd.rpc_getNameInfo(domain) if response['error'] is not None: return { - "error": response['error']['message'] + "error": { + "message": response['error']['message'] + } } return response['result'] @@ -185,7 +215,9 @@ def renewDomain(account,domain): if account_name == False: return { - "error": "Invalid account" + "error": { + "message": "Invalid account" + } } response = hsw.sendRENEW(account_name,password,domain) @@ -207,7 +239,9 @@ def setDNS(account,domain,records): if account_name == False: return { - "error": "Invalid account" + "error": { + "message": "Invalid account" + } } records = json.loads(records) @@ -270,7 +304,9 @@ def revealAuction(account,domain): if account_name == False: return { - "error": "Invalid account" + "error": { + "message": "Invalid account" + } } try: @@ -304,7 +340,9 @@ def bid(account,domain,bid,blind): if account_name == False: return { - "error": "Invalid account" + "error": { + "message": "Invalid account" + } } bid = int(bid)*1000000 @@ -315,7 +353,9 @@ def bid(account,domain,bid,blind): return response except Exception as e: return { - "error": str(e) + "error": { + "message": str(e) + } } @@ -325,7 +365,9 @@ def openAuction(account,domain): if account_name == False: return { - "error": "Invalid account" + "error": { + "message": "Invalid account" + } } try: @@ -333,5 +375,133 @@ def openAuction(account,domain): return response except Exception as e: return { - "error": str(e) - } \ No newline at end of file + "error": { + "message": str(e) + } + } + + + +def transfer(account,domain,address): + account_name = check_account(account) + password = ":".join(account.split(":")[1:]) + + if account_name == False: + return { + "error": { + "message": "Invalid account" + } + } + + try: + response = hsw.sendTRANSFER(account_name,password,domain,address) + return response + except Exception as e: + return { + "error": { + "message": str(e) + } + } + +def finalize(account,domain): + account_name = check_account(account) + password = ":".join(account.split(":")[1:]) + + if account_name == False: + return { + "error": { + "message": "Invalid account" + } + } + + try: + response = hsw.sendFINALIZE(account_name,password,domain) + return response + except Exception as e: + return { + "error": { + "message": str(e) + } + } + +def cancelTransfer(account,domain): + account_name = check_account(account) + password = ":".join(account.split(":")[1:]) + + if account_name == False: + return { + "error": { + "message": "Invalid account" + } + } + + try: + response = hsw.rpc_selectWallet(account_name) + if response['error'] is not None: + return { + "error": { + "message": response['error']['message'] + } + } + response = hsw.rpc_walletPassphrase(password,10) + if response['error'] is not None: + return { + "error": { + "message": response['error']['message'] + } + } + response = hsw.rpc_sendCANCEL(domain) + return response + except Exception as e: + return { + "error": { + "message": str(e) + } + } + +def revoke(account,domain): + account_name = check_account(account) + password = ":".join(account.split(":")[1:]) + + if account_name == False: + return { + "error": { + "message": "Invalid account" + } + } + + try: + response = hsw.sendREVOKE(account_name,password,domain) + return response + except Exception as e: + return { + "error": { + "message": str(e) + } + } + +def generateReport(account): + domains = getDomains(account) + format = str('{name},{expiry},{value},{maxBid}') + + lines = [format.replace("{","").replace("}","")] + for domain in domains: + line = format.replace("{name}",domain['name']) + expiry = "N/A" + expiryBlock = "N/A" + if 'daysUntilExpire' in domain['stats']: + days = domain['stats']['daysUntilExpire'] + # Convert to dateTime + expiry = datetime.now() + timedelta(days=days) + expiry = expiry.strftime("%d/%m/%Y %H:%M:%S") + expiryBlock = str(domain['stats']['renewalPeriodEnd']) + + line = line.replace("{expiry}",expiry) + line = line.replace("{state}",domain['state']) + line = line.replace("{expiryBlock}",expiryBlock) + line = line.replace("{value}",str(domain['value']/1000000)) + line = line.replace("{maxBid}",str(domain['highest']/1000000)) + line = line.replace("{openHeight}",str(domain['height'])) + lines.append(line) + + return lines \ No newline at end of file diff --git a/main.py b/main.py index 71f3585..391ad72 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import json +import random from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory,send_file import os import dotenv @@ -18,6 +19,7 @@ qrcode = QRcode(app) # Change this if network fees change fees = 0.02 +revokeCheck = random.randint(100000,999999) @app.route('/') @@ -346,11 +348,124 @@ def manage(domain: str): raw_dns = str(dns).replace("'",'"') dns = render.dns(dns) + errorMessage = request.args.get("error") + if errorMessage == None: + errorMessage = "" + address = request.args.get("address") + if address == None: + address = "" + + finalize_time = "" + # Check if the domain is in transfer + if domain_info['info']['transfer'] != 0: + current_block = account_module.getBlockHeight() + finalize_valid = domain_info['info']['transfer']+288 + finalize_blocks = finalize_valid - current_block + if finalize_blocks > 0: + finalize_time = "in "+ str(finalize_blocks) + " blocks (~" + str(round(finalize_blocks/6)) + " hours)" + else: + finalize_time = "now" return render_template("manage.html", account=account, sync=account_module.getNodeSync(), - domain=domain,expiry=expiry, dns=dns,raw_dns=urllib.parse.quote(raw_dns)) + error=errorMessage, address=address, + domain=domain,expiry=expiry, dns=dns, + raw_dns=urllib.parse.quote(raw_dns), + finalize_time=finalize_time) +@app.route('/manage//finalize') +def finalize(domain: str): + # 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() + print(domain) + response = account_module.finalize(request.cookies.get("account"),domain) + if 'error' in response: + print(response) + return redirect("/manage/" + domain + "?error=" + response['error']['message']) + + return redirect("/success?tx=" + response['hash']) + +@app.route('/manage//cancel') +def cancelTransfer(domain: str): + # 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() + print(domain) + response = account_module.cancelTransfer(request.cookies.get("account"),domain) + if 'error' in response: + if response['error'] != None: + print(response) + return redirect("/manage/" + domain + "?error=" + response['error']['message']) + + return redirect("/success?tx=" + response['result']['hash']) + +@app.route('/manage//revoke') +def revokeInit(domain: str): + # 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() + + content = f"Are you sure you want to revoke {domain}/?
" + content += f"This will return the domain to the auction pool and you will lose any funds spent on the domain.
" + content += f"This cannot be undone after the transaction is sent.

" + content += f"Please enter your password to confirm." + + cancel = f"/manage/{domain}" + confirm = f"/manage/{domain}/revoke/confirm" + action = f"Revoke {domain}/" + + + return render_template("confirm-password.html", account=account_module.check_account(request.cookies.get("account")), + sync=account_module.getNodeSync(),action=action, + content=content,cancel=cancel,confirm=confirm,check=revokeCheck) + +@app.route('/manage//revoke/confirm', methods=["POST"]) +def revokeConfirm(domain: str): + # 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() + password = request.form.get("password") + check = request.form.get("check") + if check != str(revokeCheck): + return redirect("/manage/" + domain + "?error=An error occurred. Please try again.") + + response = account_module.check_password(request.cookies.get("account"),password) + if response == False: + return redirect("/manage/" + domain + "?error=Invalid password") + + + response = account_module.revoke(request.cookies.get("account"),domain) + if 'error' in response: + print(response) + return redirect("/manage/" + domain + "?error=" + response['error']['message']) + + return redirect("/success?tx=" + response['hash']) + @app.route('/manage//renew') def renew(domain: str): # Check if the user is logged in @@ -365,7 +480,6 @@ def renew(domain: str): response = account_module.renewDomain(request.cookies.get("account"),domain) return redirect("/success?tx=" + response['hash']) - @app.route('/manage//edit') def editPage(domain: str): # Check if the user is logged in @@ -451,6 +565,61 @@ def editSave(domain: str): return redirect("/manage/" + domain + "/edit?dns="+raw_dns+"&error=" + str(response['error'])) return redirect("/success?tx=" + response['hash']) +@app.route('/manage//transfer') +def transfer(domain): + 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.args.get("address") + + if address is None: + return redirect("/manage/" + domain + "?error=Invalid address") + + address_check = account_module.check_address(address,True,True) + if not address_check: + return redirect("/send?message=Invalid address&address=" + address) + + address = address_check + + toAddress = address + if request.form.get('address') != address: + toAddress = request.args.get('address') + "
" + address + + action = f"Send {domain}/ to {request.form.get('address')}" + content = f"Are you sure you want to send {domain}/ to {toAddress}

" + content += f"This requires sending a finalize transaction 2 days after the transfer is initiated." + + cancel = f"/manage/{domain}?address={address}" + confirm = f"/manage/{domain}/transfer/confirm?address={address}" + + + return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")), + sync=account_module.getNodeSync(),action=action, + content=content,cancel=cancel,confirm=confirm) + +@app.route('/manage//transfer/confirm') +def transferConfirm(domain): + 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.args.get("address") + response = account_module.transfer(request.cookies.get("account"),domain,address) + if 'error' in response: + return redirect("/manage/" + domain + "?error=" + response['error']) + + return redirect("/success?tx=" + response['hash']) + + @app.route('/auction/') def auction(domain): # Check if the user is logged in @@ -682,6 +851,17 @@ def logout(): response.set_cookie("account", "", expires=0) return response + +@app.route('/report') +def report(): + # 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")) + + return jsonify(account_module.generateReport(account)) + #endregion #region Assets and default pages diff --git a/templates/confirm-password.html b/templates/confirm-password.html new file mode 100644 index 0000000..d87534e --- /dev/null +++ b/templates/confirm-password.html @@ -0,0 +1,85 @@ + + + + + + + Confirm - FireWallet + + + + + + + + + + + + + +
+ +
+
+ +
+

Are you sure you want to do this?

+
+
+

{{action}}

+
{{subtitle}}
+

{{content|safe}}

+
Cancel +
+
+
+
+
+
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/templates/manage.html b/templates/manage.html index b3a89fe..83fd6b4 100644 --- a/templates/manage.html +++ b/templates/manage.html @@ -91,7 +91,7 @@

Transfer

-
+
+
+
{{domain}}/ is transferring. You can finalize your transfer {{finalize_time}}. 
+ +