from decimal import Decimal from functools import cache import json from flask import ( Flask, make_response, redirect, request, jsonify, render_template, send_from_directory, send_file, ) import os import json import requests from datetime import datetime import dotenv import hsd import indexerClasses import db from datetime import datetime, timezone from indexerClasses import Block, Transaction, Covenant dotenv.load_dotenv() app = Flask(__name__) dbCon = db.DBConnection() def find(name, path): for root, dirs, files in os.walk(path): if name in files: return os.path.join(root, name) # region Main routes @app.route("/") def index(): # txs = hsd.get_mempool() # mempool_info = hsd.get_mempool_info() # if mempool_info['result']: # mempool_info = mempool_info['result'] # mempool_info['txs'] = txs return render_template("index.html") @app.route("/search") def search(): if not request.args.get("q"): return render_template("index.html") query = request.args.get("q") tx = dbCon.getTransaction(query) if tx: return redirect(f"/tx/{query}") block = dbCon.getBlock(query) if block: return redirect(f"/block/{query}") block = dbCon.getBlockHash(query) if block: return redirect(f"/block/{query}") name = hsd.get_name(query) if not name['error']: return redirect(f"/name/{query}") return render_template("index.html",message="No results found",query=query) @app.route("/name") def name(): if request.args.get("name"): name = request.args.get("name") data = hsd.get_name(name) dns = hsd.get_name_resource(name) data = json.dumps(data, indent=4) + "

" + json.dumps(dns, indent=4) return render_template("data.html", data=data) else: return render_template("index.html") @app.route("/tx/") def txPage(tx): tx = dbCon.getTransaction(tx) if tx: return render_template("tx.html", tx=tx) return render_template("index.html",message="Transaction not found") @app.route("/block/") def blockPage(block): block = dbCon.getBlock(block) if block: return render_template("block.html", block=block) return render_template("index.html",message="Block not found") @app.route("/address/
") def addressPage(address): # if address: # return render_template("address.html", address=address) return render_template("index.html",message="Not implemented") @app.route("/") def catch_all(path: str): if os.path.isfile("templates/" + path): return render_template(path) # Try with .html if os.path.isfile("templates/" + path + ".html"): return render_template(path + ".html") if os.path.isfile("templates/" + path.strip("/") + ".html"): return render_template(path.strip("/") + ".html") # Try to find a file matching if path.count("/") < 1: # Try to find a file matching filename = find(path, "templates") if filename: return send_file(filename) return render_template("404.html"), 404 # endregion # region API routes @app.route("/api/v1/version") def api_version(): return jsonify({"version": "1.0.0"}) @app.route("/api/v1/tx/") def api_tx(txid): tx = dbCon.getTransaction(txid) if tx: return jsonify(tx.toJSON()) else: return jsonify({"error": "tx not found"}), 404 @app.route("/api/v1/block/") def api_block(blockheight): block = dbCon.getBlock(blockheight) if block: print("Found block from height") return jsonify(block.toJSON()) block = dbCon.getBlockHash(blockheight) if block: print("Found block from hash") return jsonify(block.toJSON()) return jsonify({"error": "block not found"}), 404 @app.route("/api/v1/name/") def api_name(name): name = hsd.get_name(name) if name: return jsonify(name) else: return jsonify({"error": "name not found"}), 404 @app.route("/api/v1/name//resource") @app.route("/api/v1/name//dns") @app.route("/api/v1/resource/") @app.route("/api/v1/dns/") def api_name_resource(name): name = hsd.get_name_resource(name) if name: return jsonify(name) else: return jsonify({"error": "name not found"}), 404 @app.route("/api/v1/address/
") def api_address(address): address = hsd.get_address(address) if address: return jsonify(address) else: return jsonify({"error": "address not found"}), 404 @app.route("/api/v1/mempool") def api_mempool(): mempool = hsd.get_mempool() if mempool: return jsonify(mempool) else: return jsonify({"error": "mempool not found"}), 404 @app.route("/api/v1/mempool/info") def api_mempool_info(): mempool_info = hsd.get_mempool_info() if mempool_info: return jsonify(mempool_info) else: return jsonify({"error": "mempool info not found"}), 404 # endregion # region Error Catching # 404 catch all @app.errorhandler(404) def not_found(e): return render_template("404.html"), 404 # endregion # region Assets routes @app.route("/assets/") def send_assets(path): if path.endswith(".json"): return send_from_directory( "templates/assets", path, mimetype="application/json" ) if os.path.isfile("templates/assets/" + path): return send_from_directory("templates/assets", path) # Try looking in one of the directories filename: str = path.split("/")[-1] if ( filename.endswith(".png") or filename.endswith(".jpg") or filename.endswith(".jpeg") or filename.endswith(".svg") ): if os.path.isfile("templates/assets/img/" + filename): return send_from_directory("templates/assets/img", filename) if os.path.isfile("templates/assets/img/favicon/" + filename): return send_from_directory("templates/assets/img/favicon", filename) return render_template("404.html"), 404 @app.route("/favicon.png") def faviconPNG(): return send_from_directory("templates/assets/img", "favicon.png") @app.route("/.well-known/") def wellknown(path): # Try to proxy to https://nathan.woodburn.au/.well-known/ req = requests.get(f"https://nathan.woodburn.au/.well-known/{path}") return make_response( req.content, 200, {"Content-Type": req.headers["Content-Type"]} ) @app.template_filter('datetimeformat') def datetimeformat(value, format='%Y-%m-%d %H:%M:%S'): return datetime.fromtimestamp(value, tz=timezone.utc).strftime(format) @app.template_filter('convert_bits_to_difficulty') def convert_bits_to_difficulty(bits): """Convert compact bits format to difficulty.""" bits_hex = f"{bits:08x}" # Convert to 8-char hex string exp = int(bits_hex[:2], 16) # First byte (exponent) coeff = int(bits_hex[2:], 16) # Remaining 3 bytes (coefficient) # Compute target from bits target = coeff * (256 ** (exp - 3)) # Maximum target (difficulty 1) max_target = 0xFFFF * (256 ** (0x1D - 3)) # Compute difficulty difficulty = Decimal(max_target) / Decimal(target) return f"{difficulty:,.0f}" @app.template_filter('hexToAscii') def hexToAscii(hex_string): # Convert the hex string to bytes bytes_obj = bytes.fromhex(hex_string) # Decode the bytes object to an ASCII string ascii_string = bytes_obj.decode('ascii') return ascii_string @app.template_filter('getTX') def getTX(txid): tx = dbCon.getTransaction(txid) if tx: return tx.toJSON() else: return {"error": "tx not found"} @app.template_filter("parse_covenant") def parse_covenant(covenant): covenant = Covenant(covenant) if covenant.name: return f"{covenant.action} {covenant.name}" elif covenant.type == 0: return f"{covenant.action}" name = dbCon.getNameByHash(covenant.nameHash) if name: return f"{covenant.action} {name}" return f"{covenant.action} Unknown Name" # endregion if __name__ == "__main__": app.run(debug=True, port=5000, host="0.0.0.0")