diff --git a/.gitignore b/.gitignore index 33a7451..01a374d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ plugins/signatures.json .venv/ -user_data/ \ No newline at end of file +user_data/ +customPlugins/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 725a071..9fb6a86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,7 @@ COPY . /app # Add mount point for data volume # VOLUME /data +RUN apk add git ENTRYPOINT ["python3"] CMD ["server.py"] diff --git a/FireWalletBrowser.bsdesign b/FireWalletBrowser.bsdesign index 539ca2e..7589626 100644 Binary files a/FireWalletBrowser.bsdesign and b/FireWalletBrowser.bsdesign differ diff --git a/README.md b/README.md index c18439c..05b6b8f 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ Then access the wallet at http://localhost:5000 Also available as a docker image: - To run using a HSD running directly on the host: ```bash @@ -47,6 +46,8 @@ If you have HSD running on a different IP/container 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) + ## Features - Basic wallet functionality - Create new wallet diff --git a/account.py b/account.py index 0844c8c..9ecb02e 100644 --- a/account.py +++ b/account.py @@ -402,11 +402,16 @@ def getWalletStatus(): def getBids(account, domain="NONE"): if domain == "NONE": - return hsw.getWalletBids(account) - - - response = hsw.getWalletBidsByName(domain,account) - return response + response = hsw.getWalletBids(account) + else: + response = hsw.getWalletBidsByName(domain,account) + # Add backup for bids with no value + bids = [] + for bid in response: + if 'value' not in bid: + bid['value'] = -1000000 + bids.append(bid) + return bids def getReveals(account,domain): return hsw.getWalletRevealsByName(domain,account) @@ -659,6 +664,52 @@ def revoke(account,domain): } } +def sendBatch(account, batch): + 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 = requests.post(f"http://x:{APIKEY}@{ip}:12039",json={ + "method": "sendbatch", + "params": [batch] + }).json() + if response['error'] is not None: + return response + if 'result' not in response: + return { + "error": { + "message": "No result" + } + } + + return response['result'] + except Exception as e: + return { + "error": { + "message": str(e) + } + } #region settingsAPIs diff --git a/main.py b/main.py index 7bf2f37..c5d9cdc 100644 --- a/main.py +++ b/main.py @@ -278,6 +278,8 @@ def auctions(): balance = account_module.getBalance(account) locked = balance['locked'] + # Round to 2 decimals + locked = round(locked, 2) # Add commas to the numbers locked = "{:,}".format(locked) @@ -289,7 +291,7 @@ def auctions(): # Sort sort = request.args.get("sort") if sort == None: - sort = "domain" + sort = "time" sort = sort.lower() sort_price = "" sort_price_next = "⬇" @@ -297,11 +299,16 @@ def auctions(): sort_state_next = "⬇" sort_domain = "" sort_domain_next = "⬇" + sort_time = "" + sort_time_next = "⬇" reverse = False direction = request.args.get("direction") if direction == None: - direction = "⬇" + if sort == "time": + direction = "⬆" + else: + direction = "⬇" if direction == "⬆": reverse = True @@ -315,6 +322,10 @@ def auctions(): sort_state = direction sort_state_next = reverseDirection(direction) domains = sorted(domains, key=lambda k: k['state'],reverse=reverse) + elif sort == "time": + sort_time = direction + sort_time_next = reverseDirection(direction) + bids = sorted(bids, key=lambda k: k['height'],reverse=reverse) else: # Sort by domain bids = sorted(bids, key=lambda k: k['name'],reverse=reverse) @@ -342,10 +353,6 @@ def auctions(): pending_reveals += 1 plugins = "" - # dashFunctions = plugins_module.getDashboardFunctions() - # for function in dashFunctions: - # 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"])) message = '' if 'message' in request.args: @@ -357,7 +364,8 @@ def auctions(): 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),reveal=pending_reveals,message=message, + sort_time=sort_time,sort_time_next=sort_time_next) @app.route('/reveal') def revealAllBids(): @@ -377,7 +385,7 @@ def revealAllBids(): return redirect("/auctions?message=No reveals pending") return redirect("/auctions?message=" + response['error']['message']) - return redirect("/success?tx=" + response['hash']) + return redirect("/success?tx=" + response['result']['hash']) @app.route('/search') @@ -1091,7 +1099,7 @@ def settings_action(action): resp = account_module.rescan() if 'error' in resp: return redirect("/settings?error=" + str(resp['error'])) - return redirect("/settings?success=Resent transactions") + return redirect("/settings?success=Rescan started") elif action == "resend": resp = account_module.resendTXs() if 'error' in resp: @@ -1292,8 +1300,8 @@ def plugins_index(): wallet_status=account_module.getWalletStatus(), plugins=plugins) -@app.route('/plugin/') -def plugin(plugin): +@app.route('/plugin//') +def plugin(ptype,plugin): # Check if the user is logged in if request.cookies.get("account") is None: return redirect("/login") @@ -1302,7 +1310,10 @@ def plugin(plugin): if not account: return redirect("/logout") + plugin = f"{ptype}/{plugin}" + if not plugins_module.pluginExists(plugin): + print(f"Plugin {plugin} not found") return redirect("/plugins") data = plugins_module.getPluginData(plugin) @@ -1322,10 +1333,10 @@ def plugin(plugin): wallet_status=account_module.getWalletStatus(), name=data['name'],description=data['description'], author=data['author'],version=data['version'], - functions=functions,error=error) + source=data['source'],functions=functions,error=error) -@app.route('/plugin//verify') -def plugin_verify(plugin): +@app.route('/plugin///verify') +def plugin_verify(ptype,plugin): # Check if the user is logged in if request.cookies.get("account") is None: return redirect("/login") @@ -1333,6 +1344,8 @@ def plugin_verify(plugin): account = account_module.check_account(request.cookies.get("account")) if not account: return redirect("/logout") + + plugin = f"{ptype}/{plugin}" if not plugins_module.pluginExists(plugin): return redirect("/plugins") @@ -1344,8 +1357,8 @@ def plugin_verify(plugin): return redirect("/plugin/" + plugin) -@app.route('/plugin//', methods=["POST"]) -def plugin_function(plugin,function): +@app.route('/plugin///', methods=["POST"]) +def plugin_function(ptype,plugin,function): # Check if the user is logged in if request.cookies.get("account") is None: return redirect("/login") @@ -1354,6 +1367,8 @@ def plugin_function(plugin,function): if not account: return redirect("/logout") + plugin = f"{ptype}/{plugin}" + if not plugins_module.pluginExists(plugin): return redirect("/plugins") @@ -1386,7 +1401,6 @@ def plugin_function(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(), name=data['name'],description=data['description'],output=response) diff --git a/plugin.py b/plugin.py index c2d5edc..0caa06b 100644 --- a/plugin.py +++ b/plugin.py @@ -3,10 +3,12 @@ import json import importlib import sys import hashlib +import subprocess def listPlugins(): plugins = [] + customPlugins = [] for file in os.listdir("plugins"): if file.endswith(".py"): if file != "main.py": @@ -14,17 +16,50 @@ def listPlugins(): if "info" not in dir(plugin): continue details = plugin.info - details["link"] = file[:-3] + details["source"] = "built-in" + details["link"] = f"plugins/{file[:-3]}" plugins.append(details) + # Check for imported plugins + if not os.path.exists("user_data/plugins.json"): + with open("user_data/plugins.json", "w") as f: + json.dump([], f) + + with open("user_data/plugins.json", "r") as f: + importurls = json.load(f) + + for importurl in importurls: + # Get only repo name + importPath = importurl.split("/")[-1].removesuffix(".git") + + # Git clone into customPlugins/ + 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 + + # Import plugins from customPlugins/ + for file in os.listdir(f"customPlugins/{importPath}"): + if file.endswith(".py"): + if file != "main.py": + plugin = importlib.import_module(f"customPlugins.{importPath}."+file[:-3]) + if "info" not in dir(plugin): + continue + details = plugin.info + details["source"] = importPath + details["link"] = f"customPlugins/{importPath}/{file[:-3]}" + plugins.append(details) + # Verify plugin signature signatures = [] try: - with open("plugins/signatures.json", "r") as f: + with open("user_data/plugin_signatures.json", "r") as f: signatures = json.load(f) except: # Write a new signatures file - with open("plugins/signatures.json", "w") as f: + with open("user_data/plugin_signatures.json", "w") as f: json.dump(signatures, f) for plugin in plugins: @@ -39,34 +74,31 @@ def listPlugins(): def pluginExists(plugin: str): - for file in os.listdir("plugins"): - if file == plugin+".py": - return True - return False + return os.path.exists(plugin+".py") def verifyPlugin(plugin: str): signatures = [] try: - with open("plugins/signatures.json", "r") as f: + with open("user_data/plugin_signatures.json", "r") as f: signatures = json.load(f) except: # Write a new signatures file - with open("plugins/signatures.json", "w") as f: + with open("user_data/plugin_signatures.json", "w") as f: json.dump(signatures, f) # Hash the plugin file pluginHash = hashPlugin(plugin) if pluginHash not in signatures: signatures.append(pluginHash) - with open("plugins/signatures.json", "w") as f: + with open("user_data/plugin_signatures.json", "w") as f: json.dump(signatures, f) def hashPlugin(plugin: str): BUF_SIZE = 65536 sha256 = hashlib.sha256() - with open("plugins/"+plugin+".py", 'rb') as f: + with open(plugin+".py", 'rb') as f: while True: data = f.read(BUF_SIZE) if not data: @@ -76,19 +108,31 @@ def hashPlugin(plugin: str): def getPluginData(pluginStr: str): - plugin = importlib.import_module("plugins."+pluginStr) + plugin = importlib.import_module(pluginStr.replace("/",".")) # Check if the plugin is verified signatures = [] try: - with open("plugins/signatures.json", "r") as f: + with open("user_data/plugin_signatures.json", "r") as f: signatures = json.load(f) except: # Write a new signatures file - with open("plugins/signatures.json", "w") as f: + with open("user_data/plugin_signatures.json", "w") as f: json.dump(signatures, f) info = plugin.info + info["source"] = "built-in" + + # Check if the plugin is in customPlugins + if pluginStr.startswith("customPlugins"): + # Get git url for dir + print(f"cd customPlugins/{pluginStr.split('/')[-2]} && git remote get-url origin") + url = subprocess.check_output(f"cd customPlugins/{pluginStr.split('/')[-2]} && git remote get-url origin", shell=True).decode("utf-8").strip() + info["source"] = url + + + + # Hash the plugin file pluginHash = hashPlugin(pluginStr) if pluginHash not in signatures: @@ -100,12 +144,12 @@ def getPluginData(pluginStr: str): def getPluginFunctions(plugin: str): - plugin = importlib.import_module("plugins."+plugin) + plugin = importlib.import_module(plugin.replace("/",".")) return plugin.functions def runPluginFunction(plugin: str, function: str, params: dict, authentication: str): - plugin_module = importlib.import_module("plugins."+plugin) + plugin_module = importlib.import_module(plugin.replace("/",".")) if function not in plugin_module.functions: return {"error": "Function not found"} @@ -118,11 +162,11 @@ def runPluginFunction(plugin: str, function: str, params: dict, authentication: # Check if the function is in the signature list signatures = [] try: - with open("plugins/signatures.json", "r") as f: + with open("user_data/plugin_signatures.json", "r") as f: signatures = json.load(f) except: # Write a new signatures file - with open("plugins/signatures.json", "w") as f: + with open("user_data/plugin_signatures.json", "w") as f: json.dump(signatures, f) # Hash the plugin file @@ -141,12 +185,12 @@ def runPluginFunction(plugin: str, function: str, params: dict, authentication: def getPluginFunctionInputs(plugin: str, function: str): - plugin = importlib.import_module("plugins."+plugin) + plugin = importlib.import_module(plugin.replace("/",".")) return plugin.functions[function]["params"] def getPluginFunctionReturns(plugin: str, function: str): - plugin = importlib.import_module("plugins."+plugin) + plugin = importlib.import_module(plugin.replace("/",".")) return plugin.functions[function]["returns"] diff --git a/plugins/batching.py b/plugins/batching.py new file mode 100644 index 0000000..e1770b2 --- /dev/null +++ b/plugins/batching.py @@ -0,0 +1,518 @@ +import json +import account +import requests +import os + + + +# Plugin Data +info = { + "name": "Batching Functions", + "description": "This is a plugin that provides multiple functions to batch transactions", + "version": "1.0", + "author": "Nathan.Woodburn/" +} +# https://hsd-dev.org/api-docs/?shell--cli#sendbatch + + +# Functions +functions = { + "transfer":{ + "name": "Batch transfer", + "type": "default", + "description": "Transfer a ton of domains", + "params": { + "domains": { + "name":"List of domains to transfer (one per line)", + "type":"longText" + }, + "address": { + "name":"Address to transfer to", + "type":"address" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "finalize":{ + "name": "Batch finalize a transfer", + "type": "default", + "description": "Finalize transferring a ton of domains", + "params": { + "domains": { + "name":"List of domains to finalize (one per line)", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "cancel":{ + "name": "Batch cancel a transfer", + "type": "default", + "description": "Cancel transferring a ton of domains", + "params": { + "domains": { + "name":"List of domains to cancel (one per line)", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "open":{ + "name": "Batch open auctions", + "type": "default", + "description": "Open auctions for a ton of domains", + "params": { + "domains": { + "name":"List of domains to open (one per line)", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "bid":{ + "name": "Batch bid on auctions", + "type": "default", + "description": "Bid on auctions for a ton of domains", + "params": { + "domains": { + "name":"List of domains to bid on (one per line)", + "type":"longText" + }, + "bid": { + "name":"Bid amount", + "type":"text" + }, + "blind": { + "name":"Blind amount", + "type":"text" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "reveal":{ + "name": "Batch reveal bids", + "type": "default", + "description": "Reveal bids for tons of auctions", + "params": { + "domains": { + "name":"List of domains to reveal (one per line)", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "redeem":{ + "name": "Batch redeem bids", + "type": "default", + "description": "Redeem lost bids to get funds back", + "params": { + "domains": { + "name":"List of domains to redeem (one per line)", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "register":{ + "name": "Batch register domains", + "type": "default", + "description": "Register domains won in auction", + "params": { + "domains": { + "name":"List of domains to redeem (one per line)", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "renew":{ + "name": "Batch renew domains", + "type": "default", + "description": "Renew a ton of domain", + "params": { + "domains": { + "name": "Domains to renew (one per line)", + "type": "longText" + } + }, + "returns": { + "status": { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "advancedBid":{ + "name": "Bid on domains with csv", + "type": "default", + "description": "Bid on domains using a csv format", + "params": { + "bids": { + "name":"List of bids in format `domain,bid,blind` (one per line)", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "advancedBatch":{ + "name": "Batch transactions with csv", + "type": "default", + "description": "Batch transactions using a csv format", + "params": { + "transactions": { + "name":"List of transactions in format `type,domain,param1,param2` (one per line) Eg.
TRANSFER,woodburn1,hs1q4rkfe5df7ss6wzhnw388hv27we0hp7ha2np0hk
OPEN,woodburn2", + "type":"longText" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + }, + "transaction": + { + "name": "Hash of the transaction", + "type": "tx" + } + } + }, + "advancedChangeLookahead":{ + "name": "Change wallet lookahead", + "type": "default", + "description": "Change the lookahead of the wallet", + "params": { + "lookahead": { + "name":"Lookahead (default 200)", + "type":"number" + } + }, + "returns": { + "status": + { + "name": "Status", + "type": "text" + } + } + } +} + +def sendBatch(batch, authentication): + response = account.sendBatch(authentication, batch) + return response + + +def transfer(params, authentication): + domains = params["domains"] + address = params["address"] + domains = domains.splitlines() + domains = [x.strip() for x in domains] + domains = [x for x in domains if x != ""] + + wallet = authentication.split(":")[0] + owned = account.getDomains(wallet) + # Only keep owned domains ["name"] + ownedNames = [domain["name"] for domain in owned] + + for domain in domains: + if domain not in ownedNames: + return { + "status":f"Domain {domain} not owned", + "transaction":None + } + + batch = [] + for domain in domains: + batch.append(['TRANSFER', domain, address]) + + response = sendBatch(batch, authentication) + if 'error' in response: + return { + "status":response['error']['message'], + "transaction":None + } + + return { + "status":"Sent batch successfully", + "transaction":response['hash'] + } + +def simple(batchType,params, authentication): + domains = params["domains"] + domains = domains.splitlines() + domains = [x.strip() for x in domains] + domains = [x for x in domains if x != ""] + + batch = [] + for domain in domains: + batch.append([batchType, domain]) + + print(batch) + response = sendBatch(batch, authentication) + if 'error' in response: + print(response) + return { + "status":response['error']['message'], + "transaction":None + } + + return { + "status":"Sent batch successfully", + "transaction":response['hash'] + } + +def finalize(params, authentication): + return simple("FINALIZE",params,authentication) + +def cancel(params, authentication): + return simple("CANCEL",params,authentication) + +def open(params, authentication): + return simple("OPEN",params,authentication) + +def bid(params, authentication): + domains = params["domains"] + domains = domains.splitlines() + domains = [x.strip() for x in domains] + domains = [x for x in domains if x != ""] + + try: + bid = float(params["bid"]) + blind = float(params["blind"]) + blind+=bid + except: + return { + "status":"Invalid bid amount", + "transaction":None + } + + batch = [] + for domain in domains: + batch.append(['BID', domain, bid, blind]) + + print(batch) + response = sendBatch(batch, authentication) + if 'error' in response: + return { + "status":response['error']['message'], + "transaction":None + } + + return { + "status":"Sent batch successfully", + "transaction":response['hash'] + } + +def reveal(params, authentication): + return simple("REVEAL",params,authentication) + +def redeem(params, authentication): + return simple("REDEEM",params,authentication) + +def register(params, authentication): + domains = params["domains"] + domains = domains.splitlines() + domains = [x.strip() for x in domains] + domains = [x for x in domains if x != ""] + + batch = [] + for domain in domains: + batch.append(['UPDATE', domain,{"records": []}]) + + print(batch) + response = sendBatch(batch, authentication) + if 'error' in response: + return { + "status":response['error']['message'], + "transaction":None + } + + return { + "status":"Sent batch successfully", + "transaction":response['hash'] + } + +def renew(params, authentication): + return simple("RENEW", params, authentication) + +def advancedBid(params, authentication): + bids = params["bids"] + bids = bids.splitlines() + bids = [x.strip() for x in bids] + bids = [x for x in bids if x != ""] + + batch = [] + for bid in bids: + # Split the bid + line = bid.split(",") + domain = line[0] + bid = float(line[1]) + blind = float(line[2]) + blind+=bid + batch.append(['BID', domain, bid, blind]) + + print(batch) + response = sendBatch(batch, authentication) + if 'error' in response: + return { + "status":response['error']['message'], + "transaction":None + } + + return { + "status":"Sent batch successfully", + "transaction":response['hash'] + } + +def advancedBatch(params, authentication): + transactions = params["transactions"] + transactions = transactions.splitlines() + transactions = [x.strip() for x in transactions] + transactions = [x for x in transactions if x != ""] + + batch = [] + for transaction in transactions: + # Split the bid + line = transaction.split(",") + line[0] = line[0].upper() + batch.append(line) + + print(batch) + response = sendBatch(batch, authentication) + if 'error' in response: + return { + "status":response['error']['message'], + "transaction":None + } + + return { + "status":"Sent batch successfully", + "transaction":response['hash'] + } + + +def advancedChangeLookahead(params, authentication): + lookahead = params["lookahead"] + 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" + + # Unlock wallet + response = requests.post(f"http://x:{APIKEY}@{ip}:12039/wallet/{wallet}/unlock", + json={"passphrase": password, "timeout": 10}) + + response = requests.patch(f"http://x:{APIKEY}@{ip}:12039/wallet/{wallet}/account/default", + json={"lookahead": lookahead}) + + + return { + "status":f"Status: {'Success' if response.status_code == 200 else 'Error'}" + } \ No newline at end of file diff --git a/plugins/customPlugins.py b/plugins/customPlugins.py new file mode 100644 index 0000000..1b9058d --- /dev/null +++ b/plugins/customPlugins.py @@ -0,0 +1,114 @@ +import json +import account +import requests +import os + +# Plugin Data +info = { + "name": "Custom Plugin Manager", + "description": "Import custom plugins from git repositories", + "version": "1.0", + "author": "Nathan.Woodburn/" +} + +# Functions +functions = { + "add":{ + "name": "Add Plugin repo", + "type": "default", + "description": "Add a plugin repo", + "params": { + "url": { + "name":"URL", + "type":"text" + } + }, + "returns": { + "status": + { + "name": "Status of the function", + "type": "text" + } + } + }, + "remove":{ + "name": "Remove Plugins", + "type": "default", + "description": "Remove a plugin repo from the list", + "params": { + "url": { + "name":"URL", + "type":"text" + } + }, + "returns": { + "status": + { + "name": "Status of the function", + "type": "text" + } + } + }, + "list":{ + "name": "List Plugins", + "type": "default", + "description": "List all imported plugins", + "params": {}, + "returns": { + "plugins": + { + "name": "List of plugins", + "type": "list" + } + } + } +} + +def add(params, authentication): + url = params["url"] + if not os.path.exists("user_data/plugins.json"): + with open("user_data/plugins.json", "w") as f: + json.dump([], f) + + with open("user_data/plugins.json", "r") as f: + importurls = json.load(f) + + # Check if the plugin is already imported + if url in importurls: + return {"status": "Plugin already imported"} + + importurls.append(url) + with open("user_data/plugins.json", "w") as f: + json.dump(importurls, f) + + return {"status": "Imported"} + + +def remove(params, authentication): + url = params["url"] + if not os.path.exists("user_data/plugins.json"): + with open("user_data/plugins.json", "w") as f: + json.dump([], f) + + with open("user_data/plugins.json", "r") as f: + importurls = json.load(f) + + # Check if the plugin is already imported + if url not in importurls: + return {"status": "Plugin not imported"} + + importurls.remove(url) + with open("user_data/plugins.json", "w") as f: + json.dump(importurls, f) + + return {"status": "Removed"} + +def list(params, authentication): + if not os.path.exists("user_data/plugins.json"): + with open("user_data/plugins.json", "w") as f: + json.dump([], f) + + with open("user_data/plugins.json", "r") as f: + importurls = json.load(f) + + return {"plugins": importurls} \ No newline at end of file diff --git a/plugins/renewal.py b/plugins/renewal.py index ac4ef44..9a9f850 100644 --- a/plugins/renewal.py +++ b/plugins/renewal.py @@ -51,10 +51,11 @@ def main(params, authentication): # 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}@127.0.0.1:12039/wallet/{wallet}/unlock', + response = requests.post(f'http://x:{api_key}@{ip}:12039/wallet/{wallet}/unlock', json={'passphrase': password, 'timeout': 600}) if response.status_code != 200: print("Failed to unlock wallet") @@ -73,11 +74,11 @@ def main(params, authentication): batchTX = "[" + ", ".join(batch) + "]" responseContent = f'{{"method": "sendbatch","params":[ {batchTX} ]}}' - response = requests.post(f'http://x:{api_key}@127.0.0.1:12039', data=responseContent) + response = requests.post(f'http://x:{api_key}@{ip}:12039', data=responseContent) if response.status_code != 200: - print("Failed to create batch") - print(f'Status code: {response.status_code}') - print(f'Response: {response.text}') + print("Failed to create batch",flush=True) + print(f'Status code: {response.status_code}',flush=True) + print(f'Response: {response.text}',flush=True) return {"status": "Failed", "transaction": "None"} batch = response.json() @@ -85,9 +86,9 @@ def main(params, authentication): print("Verifying tx...") if batch["error"]: if batch["error"] != "": - print("Failed to verify batch") - print(batch["error"]["message"]) - return {"status": "Failed", "transaction": "None"} + print("Failed to verify batch",flush=True) + print(batch["error"]["message"],flush=True) + return {"status": f"Failed: {batch['error']['message']}", "transaction": "None"} if 'result' in batch: if batch['result'] != None: diff --git a/render.py b/render.py index 2229394..1629e6e 100644 --- a/render.py +++ b/render.py @@ -189,6 +189,7 @@ def bidDomains(bids,domains, sortState=False): bidValue = round(bidValue, 2) blind = lockup - bidValue bidValue = "{:,}".format(bidValue) + blind = round(blind, 2) blind = "{:,}".format(blind) bidDisplay = f'{bidValue} HNS + {blind} HNS blind' @@ -198,6 +199,7 @@ def bidDomains(bids,domains, sortState=False): html += f"{domain['name']}" html += f"{domain['state']}" html += f"{bidDisplay}" + html += f"{bid['height']}" html += "" else: for domain in domains: diff --git a/templates/404.html b/templates/404.html index 8fd5248..9043c5e 100644 --- a/templates/404.html +++ b/templates/404.html @@ -35,7 +35,6 @@ -
diff --git a/templates/auction.html b/templates/auction.html index a382c8d..11cdf3c 100644 --- a/templates/auction.html +++ b/templates/auction.html @@ -35,7 +35,6 @@ -
diff --git a/templates/auctions.html b/templates/auctions.html index b02626d..2491973 100644 --- a/templates/auctions.html +++ b/templates/auctions.html @@ -35,7 +35,6 @@ -
@@ -125,6 +124,7 @@ Domain{{sort_domain}} State{{sort_state}} Bid{{sort_price}} + Block{{sort_time}} diff --git a/templates/components/dashboard-plugin.html b/templates/components/dashboard-plugin.html index c6fdb75..12aeb15 100644 --- a/templates/components/dashboard-plugin.html +++ b/templates/components/dashboard-plugin.html @@ -2,7 +2,7 @@
{{name}}
-
{{output}}
+
{{output | safe}}
\ No newline at end of file diff --git a/templates/components/tx.html b/templates/components/tx.html index c1e0c1e..88f3575 100644 --- a/templates/components/tx.html +++ b/templates/components/tx.html @@ -1,4 +1,5 @@ TX: {{tx}} Check your transaction on a block explorer Niami -3xpl \ No newline at end of file +3xpl +Cymon.de \ No newline at end of file diff --git a/templates/confirm-password.html b/templates/confirm-password.html index 87981fc..0da1c89 100644 --- a/templates/confirm-password.html +++ b/templates/confirm-password.html @@ -35,7 +35,6 @@ -
diff --git a/templates/confirm.html b/templates/confirm.html index 8a10cc8..9e842c3 100644 --- a/templates/confirm.html +++ b/templates/confirm.html @@ -35,7 +35,6 @@ -
diff --git a/templates/edit.html b/templates/edit.html index 143cb6c..3d4a511 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -35,7 +35,6 @@ -
diff --git a/templates/index.html b/templates/index.html index 8fb4911..46ba406 100644 --- a/templates/index.html +++ b/templates/index.html @@ -35,7 +35,6 @@ -
diff --git a/templates/manage.html b/templates/manage.html index 44d948f..c9d2808 100644 --- a/templates/manage.html +++ b/templates/manage.html @@ -35,7 +35,6 @@ -
diff --git a/templates/message.html b/templates/message.html index d82ca43..84d2779 100644 --- a/templates/message.html +++ b/templates/message.html @@ -35,7 +35,6 @@ -
diff --git a/templates/plugin-output.html b/templates/plugin-output.html index 6569b87..723cdd6 100644 --- a/templates/plugin-output.html +++ b/templates/plugin-output.html @@ -35,7 +35,6 @@ -
diff --git a/templates/plugin.html b/templates/plugin.html index ca9291f..ac08512 100644 --- a/templates/plugin.html +++ b/templates/plugin.html @@ -35,7 +35,6 @@ -
@@ -67,7 +66,7 @@

{{name}}

{{description}}

-
Author: {{author}}
Version: {{version}}
{{functions|safe}} +
Author: {{author}}
Version: {{version}}
Source: {{source}}
{{functions|safe}}