feat: Add initial front design
All checks were successful
Build Docker / BuildImage (push) Successful in 42s
BIN
FireExplorer.bsdesign
Normal file
74
db.py
Normal file
@ -0,0 +1,74 @@
|
||||
import mysql.connector
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
import indexerClasses
|
||||
|
||||
DB_HOST = os.getenv("DB_HOST")
|
||||
DB_USER = os.getenv("DB_USER")
|
||||
DB_PASS = os.getenv("DB_PASSWORD")
|
||||
DB_NAME = os.getenv("DB_NAME")
|
||||
|
||||
class DBConnection:
|
||||
def __init__(self):
|
||||
self.conn = connect()
|
||||
|
||||
def __enter__(self):
|
||||
return self.conn
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.conn.close()
|
||||
|
||||
def getBlock(self, blockheight) -> indexerClasses.Block | None:
|
||||
with self.conn.cursor() as cursor:
|
||||
try:
|
||||
blockheight = int(blockheight)
|
||||
cursor.execute("SELECT * FROM blocks WHERE height = %s", (str(blockheight),))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return indexerClasses.Block(result)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def getBlockHash(self, blockhash) -> indexerClasses.Block | None:
|
||||
with self.conn.cursor() as cursor:
|
||||
cursor.execute("SELECT * FROM blocks WHERE hash = %s", (blockhash,))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return indexerClasses.Block(result)
|
||||
else:
|
||||
return None
|
||||
|
||||
def getTransaction(self, txid) -> indexerClasses.Transaction | None:
|
||||
with self.conn.cursor() as cursor:
|
||||
cursor.execute("SELECT * FROM transactions WHERE hash = %s", (txid,))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return indexerClasses.Transaction(result)
|
||||
else:
|
||||
return None
|
||||
|
||||
def getNameByHash(self, hash) -> str | None:
|
||||
with self.conn.cursor() as cursor:
|
||||
cursor.execute("SELECT name FROM names WHERE namehash = %s", (hash,))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return result[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
def connect():
|
||||
return mysql.connector.connect(
|
||||
host=DB_HOST,
|
||||
user=DB_USER,
|
||||
password=DB_PASS,
|
||||
database=DB_NAME,
|
||||
charset='utf8mb4',
|
||||
collation='utf8mb4_unicode_ci',
|
||||
)
|
6
hsd.py
@ -22,9 +22,6 @@ HSD_URL = f'http://x:{HSD_API_KEY}@{HSD_IP}:{HSD_PORT}'
|
||||
if os.getenv("HSD_URL"):
|
||||
HSD_URL = os.getenv("HSD_URL")
|
||||
|
||||
|
||||
print(f"Using HSD_URL: {HSD_URL}")
|
||||
|
||||
def get_tx(txid):
|
||||
return requests.get(f"{HSD_URL}/tx/{txid}").json()
|
||||
|
||||
@ -48,3 +45,6 @@ def get_mempool():
|
||||
|
||||
def get_mempool_info():
|
||||
return requests.post(HSD_URL, json={"method": "getmempoolinfo"}).json()
|
||||
|
||||
def get_name_from_hash(hash):
|
||||
return requests.post(HSD_URL, json={"method": "getnamebyhash", "params": [hash]}).json()
|
270
indexerClasses.py
Normal file
@ -0,0 +1,270 @@
|
||||
import json
|
||||
import asyncio
|
||||
import requests
|
||||
|
||||
class Block:
|
||||
def __init__(self, data):
|
||||
if isinstance(data, dict):
|
||||
self.hash = data["hash"]
|
||||
self.height = data["height"]
|
||||
self.depth = data["depth"]
|
||||
self.version = data["version"]
|
||||
self.prevBlock = data["prevBlock"]
|
||||
self.merkleRoot = data["merkleRoot"]
|
||||
self.witnessRoot = data["witnessRoot"]
|
||||
self.treeRoot = data["treeRoot"]
|
||||
self.reservedRoot = data["reservedRoot"]
|
||||
self.time = data["time"]
|
||||
self.bits = data["bits"]
|
||||
self.nonce = data["nonce"]
|
||||
self.extraNonce = data["extraNonce"]
|
||||
self.mask = data["mask"]
|
||||
self.txs = data["txs"]
|
||||
elif isinstance(data, list) or isinstance(data, tuple):
|
||||
self.hash = data[0]
|
||||
self.height = data[1]
|
||||
self.depth = data[2]
|
||||
self.version = data[3]
|
||||
self.prevBlock = data[4]
|
||||
self.merkleRoot = data[5]
|
||||
self.witnessRoot = data[6]
|
||||
self.treeRoot = data[7]
|
||||
self.reservedRoot = data[8]
|
||||
self.time = data[9]
|
||||
self.bits = data[10]
|
||||
self.nonce = data[11]
|
||||
self.extraNonce = data[12]
|
||||
self.mask = data[13]
|
||||
self.txs = json.loads(data[14])
|
||||
else:
|
||||
raise ValueError("Invalid data type")
|
||||
|
||||
def __str__(self):
|
||||
return f"Block {self.height}"
|
||||
|
||||
def toJSON(self) -> dict:
|
||||
return {
|
||||
"hash": self.hash,
|
||||
"height": self.height,
|
||||
"depth": self.depth,
|
||||
"version": self.version,
|
||||
"prevBlock": self.prevBlock,
|
||||
"merkleRoot": self.merkleRoot,
|
||||
"witnessRoot": self.witnessRoot,
|
||||
"treeRoot": self.treeRoot,
|
||||
"reservedRoot": self.reservedRoot,
|
||||
"time": self.time,
|
||||
"bits": self.bits,
|
||||
"nonce": self.nonce,
|
||||
"extraNonce": self.extraNonce,
|
||||
"mask": self.mask,
|
||||
"txs": self.txs
|
||||
}
|
||||
|
||||
class Transaction:
|
||||
def __init__(self, data):
|
||||
if isinstance(data, dict):
|
||||
self.hash = data["hash"]
|
||||
self.witnessHash = data["witnessHash"]
|
||||
self.fee = data["fee"]
|
||||
self.rate = data["rate"]
|
||||
self.mtime = data["mtime"]
|
||||
self.block = data["block"]
|
||||
self.index = data["index"]
|
||||
self.version = data["version"]
|
||||
self.inputs = data["inputs"]
|
||||
self.outputs = data["outputs"]
|
||||
self.locktime = data["locktime"]
|
||||
self.hex = data["hex"]
|
||||
elif isinstance(data, list) or isinstance(data, tuple):
|
||||
self.hash = data[0]
|
||||
self.witnessHash = data[1]
|
||||
self.fee = data[2]
|
||||
self.rate = data[3]
|
||||
self.mtime = data[4]
|
||||
self.block = data[5]
|
||||
self.index = data[6]
|
||||
self.version = data[7]
|
||||
# Load inputs with Input class
|
||||
self.inputs = []
|
||||
for input in json.loads(data[8]):
|
||||
self.inputs.append(Input(input))
|
||||
self.outputs = []
|
||||
for output in json.loads(data[9]):
|
||||
self.outputs.append(Output(output))
|
||||
self.locktime = data[10]
|
||||
self.hex = data[11]
|
||||
else:
|
||||
raise ValueError("Invalid data type")
|
||||
|
||||
def __str__(self):
|
||||
return f"Transaction {self.hash}"
|
||||
|
||||
def toJSON(self) -> dict:
|
||||
return {
|
||||
"hash": self.hash,
|
||||
"witnessHash": self.witnessHash,
|
||||
"fee": self.fee,
|
||||
"rate": self.rate,
|
||||
"mtime": self.mtime,
|
||||
"block": self.block,
|
||||
"index": self.index,
|
||||
"version": self.version,
|
||||
"inputs": [input.toJSON() for input in self.inputs],
|
||||
"outputs": [output.toJSON() for output in self.outputs],
|
||||
"locktime": self.locktime,
|
||||
"hex": self.hex
|
||||
}
|
||||
|
||||
class Input:
|
||||
|
||||
def __init__(self, data):
|
||||
if isinstance(data, dict):
|
||||
self.prevout = data["prevout"]
|
||||
self.witness = data["witness"]
|
||||
self.sequence = data["sequence"]
|
||||
self.address = None
|
||||
self.coin = None
|
||||
if "address" in data:
|
||||
self.address = data["address"]
|
||||
if "coin" in data:
|
||||
self.coin = Coin(data["coin"])
|
||||
else:
|
||||
raise ValueError("Invalid data type")
|
||||
|
||||
def __str__(self):
|
||||
return f"Input {self.prevout['hash']} {self.coin}"
|
||||
|
||||
def toJSON(self) -> dict:
|
||||
return {
|
||||
"prevout": self.prevout,
|
||||
"witness": self.witness,
|
||||
"sequence": self.sequence,
|
||||
"address": self.address,
|
||||
"coin": self.coin.toJSON() if self.coin else None
|
||||
}
|
||||
|
||||
class Output:
|
||||
|
||||
def __init__(self, data):
|
||||
if isinstance(data, dict):
|
||||
self.value = data["value"]
|
||||
self.address = data["address"]
|
||||
self.covenant = Covenant(data["covenant"])
|
||||
else:
|
||||
raise ValueError("Invalid data type")
|
||||
|
||||
def __str__(self):
|
||||
return f"Output {self.value} {self.address} {self.covenant}"
|
||||
|
||||
def toJSON(self) -> dict:
|
||||
return {
|
||||
"value": self.value,
|
||||
"address": self.address,
|
||||
"covenant": self.covenant.toJSON()
|
||||
}
|
||||
|
||||
def hex_to_ascii(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
|
||||
|
||||
class Covenant:
|
||||
def __init__(self, data):
|
||||
if isinstance(data, dict):
|
||||
self.type = data["type"]
|
||||
self.action = data["action"]
|
||||
self.items = data["items"]
|
||||
self.nameHash = None
|
||||
self.height = None
|
||||
self.name = None
|
||||
self.flags = None
|
||||
self.hash = None
|
||||
self.nonce = None
|
||||
self.recordData = None
|
||||
self.blockHash = None
|
||||
self.version = None
|
||||
self.Address = None
|
||||
self.claimHeight = None
|
||||
self.renewalCount = None
|
||||
|
||||
|
||||
if self.type > 0: # All but NONE
|
||||
self.nameHash = self.items[0]
|
||||
self.height = self.items[1]
|
||||
|
||||
if self.type == 1: # CLAIM
|
||||
self.flags = self.items[3]
|
||||
|
||||
if self.type in [1,2,3]: # CLAIM, OPEN, BID
|
||||
self.name = hex_to_ascii(self.items[2])
|
||||
|
||||
if self.type == 3: # BID
|
||||
self.hash = self.items[3]
|
||||
|
||||
if self.type == 4: # REVEAL
|
||||
self.nonce = self.items[2]
|
||||
|
||||
if self.type in [6,7]: # REGISTER, UPDATE
|
||||
self.recordData = self.items[2]
|
||||
|
||||
if self.type == 6: # REGISTER
|
||||
self.blockHash = self.items[3]
|
||||
|
||||
if self.type == 8: # RENEW
|
||||
self.blockHash = self.items[2]
|
||||
|
||||
if self.type == 9: # TRANSFER
|
||||
self.version = self.items[2]
|
||||
self.Address = self.items[3]
|
||||
|
||||
if self.type == 10: # FINALIZE
|
||||
self.name = hex_to_ascii(self.items[2])
|
||||
self.flags = self.items[3]
|
||||
self.claimHeight= self.items[4]
|
||||
self.renewalCount = self.items[5]
|
||||
self.blockHash = self.items[6]
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid data type")
|
||||
|
||||
def __str__(self):
|
||||
return self.toString()
|
||||
|
||||
def toString(self):
|
||||
return self.action
|
||||
|
||||
def toJSON(self) -> dict:
|
||||
return {
|
||||
"type": self.type,
|
||||
"action": self.action,
|
||||
"items": self.items
|
||||
}
|
||||
|
||||
class Coin:
|
||||
def __init__(self, data):
|
||||
if isinstance(data, dict):
|
||||
self.version = data["version"]
|
||||
self.height = data["height"]
|
||||
self.value = data["value"]
|
||||
self.address = data["address"]
|
||||
self.covenant = Covenant(data["covenant"])
|
||||
self.coinbase = data["coinbase"]
|
||||
else:
|
||||
raise ValueError("Invalid data type")
|
||||
|
||||
def __str__(self):
|
||||
return f"Coin {self.value} {self.address} {self.covenant}"
|
||||
|
||||
|
||||
def toJSON(self) -> dict:
|
||||
return {
|
||||
"version": self.version,
|
||||
"height": self.height,
|
||||
"value": self.value,
|
||||
"address": self.address,
|
||||
"covenant": self.covenant.toJSON(),
|
||||
"coinbase": self.coinbase
|
||||
}
|
@ -2,3 +2,4 @@ flask
|
||||
gunicorn
|
||||
requests
|
||||
python-dotenv
|
||||
mysql-connector-python
|
228
server.py
@ -1,3 +1,4 @@
|
||||
from decimal import Decimal
|
||||
from functools import cache
|
||||
import json
|
||||
from flask import (
|
||||
@ -16,77 +17,54 @@ 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)
|
||||
|
||||
# Assets routes
|
||||
@app.route("/assets/<path:path>")
|
||||
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
|
||||
|
||||
|
||||
# region Special routes
|
||||
@app.route("/favicon.png")
|
||||
def faviconPNG():
|
||||
return send_from_directory("templates/assets/img", "favicon.png")
|
||||
|
||||
|
||||
@app.route("/.well-known/<path:path>")
|
||||
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"]}
|
||||
)
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
# region Main routes
|
||||
@app.route("/")
|
||||
def index():
|
||||
txs = hsd.get_mempool()
|
||||
# txs = hsd.get_mempool()
|
||||
|
||||
mempool_info = hsd.get_mempool_info()
|
||||
if mempool_info['result']:
|
||||
mempool_info = mempool_info['result']
|
||||
# mempool_info = hsd.get_mempool_info()
|
||||
# if mempool_info['result']:
|
||||
# mempool_info = mempool_info['result']
|
||||
|
||||
mempool = f"Total Transactions: {len(txs)}<br><br>"
|
||||
for txid in txs:
|
||||
tx = hsd.get_tx(txid)
|
||||
mempool += f"<a href='/tx?tx={txid}' target='_blank'>{txid}</a>: {len(tx['inputs'])} inputs, {len(tx['outputs'])} outputs, fee: {tx['fee']/1000000}, Total Value: {sum([output['value']/1000000 for output in tx['outputs']]):,.2f} HNS<br><br>"
|
||||
# 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)
|
||||
|
||||
return render_template("index.html",mempool=mempool)
|
||||
|
||||
@app.route("/name")
|
||||
def name():
|
||||
@ -99,21 +77,25 @@ def name():
|
||||
else:
|
||||
return render_template("index.html")
|
||||
|
||||
@app.route("/tx")
|
||||
def tx():
|
||||
if request.args.get("tx"):
|
||||
tx = hsd.get_tx(request.args.get("tx"))
|
||||
return render_template("data.html", data=json.dumps(tx, indent=4))
|
||||
else:
|
||||
return render_template("index.html")
|
||||
@app.route("/tx/<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 block():
|
||||
if request.args.get("block"):
|
||||
block = hsd.get_block(request.args.get("block"))
|
||||
return render_template("data.html", data=json.dumps(block, indent=4))
|
||||
else:
|
||||
return render_template("index.html")
|
||||
@app.route("/block/<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/<address>")
|
||||
def addressPage(address):
|
||||
# if address:
|
||||
# return render_template("address.html", address=address)
|
||||
return render_template("index.html",message="Not implemented")
|
||||
|
||||
|
||||
@app.route("/<path:path>")
|
||||
@ -148,18 +130,24 @@ def api_version():
|
||||
|
||||
@app.route("/api/v1/tx/<txid>")
|
||||
def api_tx(txid):
|
||||
tx = hsd.get_tx(txid)
|
||||
tx = dbCon.getTransaction(txid)
|
||||
if tx:
|
||||
return jsonify(tx)
|
||||
return jsonify(tx.toJSON())
|
||||
else:
|
||||
return jsonify({"error": "tx not found"}), 404
|
||||
|
||||
@app.route("/api/v1/block/<blockhash>")
|
||||
def api_block(blockhash):
|
||||
block = hsd.get_block(blockhash)
|
||||
@app.route("/api/v1/block/<blockheight>")
|
||||
def api_block(blockheight):
|
||||
block = dbCon.getBlock(blockheight)
|
||||
if block:
|
||||
return jsonify(block)
|
||||
else:
|
||||
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/<name>")
|
||||
@ -213,7 +201,97 @@ def api_mempool_info():
|
||||
def not_found(e):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
# endregion
|
||||
# region Assets routes
|
||||
@app.route("/assets/<path:path>")
|
||||
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/<path:path>")
|
||||
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")
|
||||
|
227
templates/address.html
Normal file
@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Address {{address.hash}} - FireExplorer</title>
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="description" content="The piping hot Handshake Explorer by Nathan.Woodburn/">
|
||||
<meta property="og:image" content="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="64px" height="64px"><span style="margin-left: 10px;">FireExplorer</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/block">Blocks</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/tx">Transactions</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/name">Names</a></li>
|
||||
</ul>
|
||||
<form action="/search"><input class="form-control" type="search" name="q" placeholder="Search anything" value="{{query}}"></form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="py-5" style="margin: auto;max-width: 1400px;">
|
||||
<h1 class="text-center">{{message}}</h1>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4>Address {{address.hash}}
|
||||
<div class="btn-group btn-group-sm gap-2" role="group" style="margin-left: 10px;"><a class="btn btn-primary" role="button" href="/block/{{block.height - 1}}"><i class="icon ion-ios-arrow-back"></i></a><a class="btn btn-primary" role="button" href="/block/{{block.height + 1}}"><i class="icon ion-ios-arrow-forward"></i></a></div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-center toast" id="toast"><span>Copied to clipboard</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>{{block.time | datetimeformat}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Difficulty</td>
|
||||
<td>{{block.bits | convert_bits_to_difficulty}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nonce</td>
|
||||
<td>{{block.nonce}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transactions</td>
|
||||
<td>{{block.txs | length}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hash</td>
|
||||
<td><span id="hash-display" style="cursor: pointer;text-decoration: underline;" onclick="copyToClipboard(this, '{{ block.hash }}','block hash')">{{ block.hash[:6] }}...{{ block.hash[-6:] }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Merkle Root</td>
|
||||
<td>{{block.merkleRoot}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Witness Root</td>
|
||||
<td>{{block.witnessRoot}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tree Root</td>
|
||||
<td>{{block.treeRoot}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text">Nullam id dolor id nibh ultricies vehicula ut id elit. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus.</p>
|
||||
<h3>Transactions</h3>{% for txid in block.txs %}
|
||||
{% set tx = txid | getTX %}
|
||||
|
||||
<div class="card bg-dark-subtle" style="margin: 10px;">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">TX: #{{tx.index}} <span style="cursor: pointer;text-decoration: underline;" onclick="copyToClipboard(this, '{{ tx.hash }}','transaction hash')">{{ tx.hash[:6] }}...{{ tx.hash[-6:] }}</span> <a href="/tx/{{tx.hash}}"><i class="icon ion-android-open"></i></a></h4>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Fee: {{"{:,.2f}".format(tx.fee / 1000000) }} HNS</h6>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Rate: {{"{:,.2f}".format(tx.rate/1000)}} doo/vB</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" style="border-right-width: 1px;">
|
||||
<div class="row">
|
||||
<div class="col-md-6" style="border-right-width: 1px;border-right-style: solid;">{% for input in tx.inputs %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">{% if input.address %}
|
||||
<a href="/address/{{input.address}}">{{ input.address }}</a>
|
||||
{% elif input.coin %}
|
||||
<a href="/address/{{input.coin.address}}">{{ input.coin.address }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">The Void</span>
|
||||
{% endif %}</div>
|
||||
<div class="col">{% if input.address %}
|
||||
|
||||
{% elif input.coin %}
|
||||
<span>{{ "{:,.2f}".format(input.coin.value/1000000) }} HNS</span>
|
||||
{% if input.coin.covenant %}
|
||||
{% if input.coin.covenant.type != 0 %}
|
||||
{{ input.coin.covenant }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- Must be a coinbase -->
|
||||
|
||||
{% if input.witness %}
|
||||
{% if input.witness | length == 1 %}
|
||||
<span>Airdrop/Name Claim</span>
|
||||
<!-- {{input}} -->
|
||||
{% else %}
|
||||
<span>Mining Reward `{{ input.witness[0] | hexToAscii }}`</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-6">{% for output in tx.outputs %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">{% if output.address %}
|
||||
<a href="/address/{{output.address}}">{{ output.address }}</a>
|
||||
{% elif output.coin %}
|
||||
<a href="/address/{{output.coin.address}}">{{ output.coin.address }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">The Void</span>
|
||||
{% endif %}</div>
|
||||
<div class="col text-end">{% if output.covenant %}
|
||||
{% if output.covenant.action == "NONE" %}
|
||||
{{"{:,.2f}".format(output.value / 1000000) }} HNS
|
||||
{% else %}
|
||||
{{ output.covenant | parse_covenant }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{"{:,.2f}".format(output.value / 1000000) }} HNS
|
||||
{% endif %}</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">Services</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
|
||||
<li><a href="https://nathan.woodburn.au/projects" target="_blank">Development</a></li>
|
||||
<li><a href="https://hnshosting.au" target="_blank">Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">About</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://nathan.woodburn.au/" target="_blank">Nathan.Woodburn/</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
|
||||
<div class="fw-bold d-flex align-items-center mb-2"><span>FireExplorer</span></div>
|
||||
<p class="text-muted">The piping hot Handshake Explorer by Nathan.Woodburn/</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © 2025 FireExplorer</p>
|
||||
<ul class="list-inline mb-0">
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-facebook">
|
||||
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter">
|
||||
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-instagram">
|
||||
<path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334"></path>
|
||||
</svg></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bold-and-dark.js"></script>
|
||||
<script src="/assets/js/copy.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
6
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
6
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
BIN
templates/assets/fonts/ionicons.eot
Normal file
11
templates/assets/fonts/ionicons.min.css
vendored
Normal file
2230
templates/assets/fonts/ionicons.svg
Normal file
After Width: | Height: | Size: 326 KiB |
BIN
templates/assets/fonts/ionicons.ttf
Normal file
BIN
templates/assets/fonts/ionicons.woff
Normal file
BIN
templates/assets/img/brands/apple.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
templates/assets/img/brands/facebook.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
templates/assets/img/brands/google.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
templates/assets/img/brands/microsoft.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
templates/assets/img/brands/twitter.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 62 KiB |
BIN
templates/assets/img/products/1.jpg
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
templates/assets/img/products/2.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
templates/assets/img/products/3.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
templates/assets/img/team/avatar1.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
templates/assets/img/team/avatar2.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
templates/assets/img/team/avatar3.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
templates/assets/img/team/avatar4.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
templates/assets/img/team/avatar5.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
templates/assets/img/team/avatar6.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
61
templates/assets/js/bold-and-dark.js
Normal file
@ -0,0 +1,61 @@
|
||||
(function() {
|
||||
"use strict"; // Start of use strict
|
||||
|
||||
function initParallax() {
|
||||
|
||||
if (!('requestAnimationFrame' in window)) return;
|
||||
if (/Mobile|Android/.test(navigator.userAgent)) return;
|
||||
|
||||
var parallaxItems = document.querySelectorAll('[data-bss-parallax]');
|
||||
|
||||
if (!parallaxItems.length) return;
|
||||
|
||||
var defaultSpeed = 0.5;
|
||||
var visible = [];
|
||||
var scheduled;
|
||||
|
||||
window.addEventListener('scroll', scroll);
|
||||
window.addEventListener('resize', scroll);
|
||||
|
||||
scroll();
|
||||
|
||||
function scroll() {
|
||||
|
||||
visible.length = 0;
|
||||
|
||||
for (var i = 0; i < parallaxItems.length; i++) {
|
||||
var rect = parallaxItems[i].getBoundingClientRect();
|
||||
var speed = parseFloat(parallaxItems[i].getAttribute('data-bss-parallax-speed'), 10) || defaultSpeed;
|
||||
|
||||
if (rect.bottom > 0 && rect.top < window.innerHeight) {
|
||||
visible.push({
|
||||
speed: speed,
|
||||
node: parallaxItems[i]
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cancelAnimationFrame(scheduled);
|
||||
|
||||
if (visible.length) {
|
||||
scheduled = requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function update() {
|
||||
|
||||
for (var i = 0; i < visible.length; i++) {
|
||||
var node = visible[i].node;
|
||||
var speed = visible[i].speed;
|
||||
|
||||
node.style.transform = 'translate3d(0, ' + (-window.scrollY * speed) + 'px, 0)';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
initParallax();
|
||||
})(); // End of use strict
|
||||
|
18
templates/assets/js/copy.js
Normal file
@ -0,0 +1,18 @@
|
||||
function copyToClipboard(element, text, description) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
showToast("Copied " + description);
|
||||
}).catch(function(error) {
|
||||
console.error("Copy failed!", error);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to show the toast notification
|
||||
function showToast(message) {
|
||||
let toast = document.getElementById("toast");
|
||||
toast.innerText = message;
|
||||
toast.classList.add("show");
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.remove("show");
|
||||
}, 2000);
|
||||
}
|
227
templates/block.html
Normal file
@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Block {{block.height}} - FireExplorer</title>
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="description" content="The piping hot Handshake Explorer by Nathan.Woodburn/">
|
||||
<meta property="og:image" content="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/ionicons.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="64px" height="64px"><span style="margin-left: 10px;">FireExplorer</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/block">Blocks</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/tx">Transactions</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/name">Names</a></li>
|
||||
</ul>
|
||||
<form action="/search"><input class="form-control" type="search" name="q" placeholder="Search anything" value="{{query}}"></form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="py-5" style="margin: auto;max-width: 1400px;">
|
||||
<h1 class="text-center">{{message}}</h1>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4>Block {{"{:,}".format(block.height)}}
|
||||
<div class="btn-group btn-group-sm gap-2" role="group" style="margin-left: 10px;"><a class="btn btn-primary" role="button" href="/block/{{block.height - 1}}"><i class="icon ion-ios-arrow-back"></i></a><a class="btn btn-primary" role="button" href="/block/{{block.height + 1}}"><i class="icon ion-ios-arrow-forward"></i></a></div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-center toast" id="toast"><span>Copied to clipboard</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>{{block.time | datetimeformat}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Difficulty</td>
|
||||
<td>{{block.bits | convert_bits_to_difficulty}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nonce</td>
|
||||
<td>{{block.nonce}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transactions</td>
|
||||
<td>{{"{:,}".format(block.txs | length)}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hash</td>
|
||||
<td><span id="hash-display" style="cursor: pointer;text-decoration: underline;" onclick="copyToClipboard(this, '{{ block.hash }}','block hash')">{{ block.hash[:6] }}...{{ block.hash[-6:] }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Merkle Root</td>
|
||||
<td>{{block.merkleRoot}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Witness Root</td>
|
||||
<td>{{block.witnessRoot}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tree Root</td>
|
||||
<td>{{block.treeRoot}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text">Nullam id dolor id nibh ultricies vehicula ut id elit. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus.</p>
|
||||
<h3>Transactions</h3>{% for txid in block.txs %}
|
||||
{% set tx = txid | getTX %}
|
||||
|
||||
<div class="card bg-dark-subtle" style="margin: 10px;">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">TX: #{{tx.index}} <span style="cursor: pointer;text-decoration: underline;" onclick="copyToClipboard(this, '{{ tx.hash }}','transaction hash')">{{ tx.hash[:6] }}...{{ tx.hash[-6:] }}</span> <a href="/tx/{{tx.hash}}"><i class="icon ion-android-open"></i></a></h4>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Fee: {{"{:,.2f}".format(tx.fee / 1000000) }} HNS</h6>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Rate: {{"{:,.2f}".format(tx.rate/1000)}} doo/vB</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" style="border-right-width: 1px;">
|
||||
<div class="row">
|
||||
<div class="col-md-6" style="border-right-width: 1px;border-right-style: solid;">{% for input in tx.inputs %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">{% if input.address %}
|
||||
<a href="/address/{{input.address}}">{{ input.address }}</a>
|
||||
{% elif input.coin %}
|
||||
<a href="/address/{{input.coin.address}}">{{ input.coin.address }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">The Void</span>
|
||||
{% endif %}</div>
|
||||
<div class="col">{% if input.address %}
|
||||
|
||||
{% elif input.coin %}
|
||||
<span>{{ "{:,.2f}".format(input.coin.value/1000000) }} HNS</span>
|
||||
{% if input.coin.covenant %}
|
||||
{% if input.coin.covenant.type != 0 %}
|
||||
{{ input.coin.covenant }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- Must be a coinbase -->
|
||||
|
||||
{% if input.witness %}
|
||||
{% if input.witness | length == 1 %}
|
||||
<span>Airdrop/Name Claim</span>
|
||||
<!-- {{input}} -->
|
||||
{% else %}
|
||||
<span>Mining Reward `{{ input.witness[0] | hexToAscii }}`</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-6">{% for output in tx.outputs %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">{% if output.address %}
|
||||
<a href="/address/{{output.address}}">{{ output.address }}</a>
|
||||
{% elif output.coin %}
|
||||
<a href="/address/{{output.coin.address}}">{{ output.coin.address }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">The Void</span>
|
||||
{% endif %}</div>
|
||||
<div class="col text-end">{% if output.covenant %}
|
||||
{% if output.covenant.action == "NONE" %}
|
||||
{{"{:,.2f}".format(output.value / 1000000) }} HNS
|
||||
{% else %}
|
||||
{{ output.covenant | parse_covenant }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{"{:,.2f}".format(output.value / 1000000) }} HNS
|
||||
{% endif %}</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">Services</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
|
||||
<li><a href="https://nathan.woodburn.au/projects" target="_blank">Development</a></li>
|
||||
<li><a href="https://hnshosting.au" target="_blank">Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">About</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://nathan.woodburn.au/" target="_blank">Nathan.Woodburn/</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
|
||||
<div class="fw-bold d-flex align-items-center mb-2"><span>FireExplorer</span></div>
|
||||
<p class="text-muted">The piping hot Handshake Explorer by Nathan.Woodburn/</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © 2025 FireExplorer</p>
|
||||
<ul class="list-inline mb-0">
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-facebook">
|
||||
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter">
|
||||
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-instagram">
|
||||
<path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334"></path>
|
||||
</svg></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bold-and-dark.js"></script>
|
||||
<script src="/assets/js/copy.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,42 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-bs-theme="dark" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nathan.Woodburn/</title>
|
||||
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
||||
<link rel="stylesheet" href="/assets/css/index.css">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Home - FireExplorer</title>
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="description" content="The piping hot Handshake Explorer by Nathan.Woodburn/">
|
||||
<meta property="og:image" content="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="spacer"></div>
|
||||
<div class="centre">
|
||||
<h1>Nathan.Woodburn/ EXPLORER ALPHA</h1>
|
||||
|
||||
<h2>MEMPOOL</h2>
|
||||
<div>
|
||||
{{ mempool |safe}}
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Search</h2>
|
||||
<div>
|
||||
<form action="/name" method="get" style="margin: 10px;">
|
||||
<input type="text" name="name" placeholder="Name">
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
<form action="/tx" method="get" style="margin: 10px;">
|
||||
<input type="text" name="tx" placeholder="TXID">
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
<form action="/block" method="get" style="margin: 10px;">
|
||||
<input type="text" name="block" placeholder="Blockhash or Height">
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="64px" height="64px"><span style="margin-left: 10px;">FireExplorer</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item"><a class="nav-link active" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/block">Blocks</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/tx">Transactions</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/name">Names</a></li>
|
||||
</ul>
|
||||
<form action="/search"><input class="form-control" type="search" name="q" placeholder="Search anything" value="{{query}}"></form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
<section class="py-5">
|
||||
<h1 class="text-center">{{message}}</h1>
|
||||
<div class="container py-5">
|
||||
<div class="row mb-4 mb-lg-5">
|
||||
<div class="col-md-8 col-xl-6 text-center mx-auto">
|
||||
<p class="fw-bold text-success mb-2">Our Services</p>
|
||||
<h2 class="fw-bold">All Star Talent</h2>
|
||||
<p class="text-muted w-lg-50">No matter the project, our team can handle it. </p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cols-2 row-cols-md-3 mx-auto" style="max-width: 700px;">
|
||||
<div class="col mb-4">
|
||||
<div class="text-center"><img class="rounded mb-3 fit-cover" width="150" height="150" src="/assets/img/team/avatar4.jpg">
|
||||
<h5 class="fw-bold mb-0"><strong>John Smith</strong></h5>
|
||||
<p class="text-muted mb-2">Erat netus</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-4">
|
||||
<div class="text-center"><img class="rounded mb-3 fit-cover" width="150" height="150" src="/assets/img/team/avatar6.jpg">
|
||||
<h5 class="fw-bold mb-0"><strong>John Smith</strong></h5>
|
||||
<p class="text-muted mb-2">Erat netus</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-4">
|
||||
<div class="text-center"><img class="rounded mb-3 fit-cover" width="150" height="150" src="/assets/img/team/avatar5.jpg">
|
||||
<h5 class="fw-bold mb-0"><strong>John Smith</strong></h5>
|
||||
<p class="text-muted mb-2">Erat netus</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-4">
|
||||
<div class="text-center"><img class="rounded mb-3 fit-cover" width="150" height="150" src="/assets/img/team/avatar3.jpg">
|
||||
<h5 class="fw-bold mb-0"><strong>John Smith</strong></h5>
|
||||
<p class="text-muted mb-2">Erat netus</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-4">
|
||||
<div class="text-center"><img class="rounded mb-3 fit-cover" width="150" height="150" src="/assets/img/team/avatar1.jpg">
|
||||
<h5 class="fw-bold mb-0"><strong>John Smith</strong></h5>
|
||||
<p class="text-muted mb-2">Erat netus</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-4">
|
||||
<div class="text-center"><img class="rounded mb-3 fit-cover" width="150" height="150" src="/assets/img/team/avatar2.jpg">
|
||||
<h5 class="fw-bold mb-0"><strong>John Smith</strong></h5>
|
||||
<p class="text-muted mb-2">Erat netus</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">Services</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
|
||||
<li><a href="https://nathan.woodburn.au/projects" target="_blank">Development</a></li>
|
||||
<li><a href="https://hnshosting.au" target="_blank">Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">About</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://nathan.woodburn.au/" target="_blank">Nathan.Woodburn/</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
|
||||
<div class="fw-bold d-flex align-items-center mb-2"><span>FireExplorer</span></div>
|
||||
<p class="text-muted">The piping hot Handshake Explorer by Nathan.Woodburn/</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © 2025 FireExplorer</p>
|
||||
<ul class="list-inline mb-0">
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-facebook">
|
||||
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter">
|
||||
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-instagram">
|
||||
<path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334"></path>
|
||||
</svg></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bold-and-dark.js"></script>
|
||||
<script src="/assets/js/copy.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
188
templates/tx.html
Normal file
@ -0,0 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Transaction {{tx.hash}} - FireExplorer</title>
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="description" content="The piping hot Handshake Explorer by Nathan.Woodburn/">
|
||||
<meta property="og:image" content="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png" media="(prefers-color-scheme: dark)">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="64px" height="64px"><span style="margin-left: 10px;">FireExplorer</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/block">Blocks</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/tx">Transactions</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/name">Names</a></li>
|
||||
</ul>
|
||||
<form action="/search"><input class="form-control" type="search" name="q" placeholder="Search anything" value="{{query}}"></form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="py-5" style="margin: auto;max-width: 1400px;">
|
||||
<h1 class="text-center">{{message}}</h1>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="container">
|
||||
<h4>Transaction {{tx.hash}}</h4>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Block</td>
|
||||
<td>{{"{:,}".format(tx.block)}} (TX: #{{"{:,}".format(tx.index)}})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fee</td>
|
||||
<td>{{"{:,.2f}".format(tx.fee / 1000000) }} HNS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rate</td>
|
||||
<td>{{"{:,.2f}".format(tx.rate/1000)}} doo/vB</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hash</td>
|
||||
<td><span id="hash-display" style="cursor: pointer;text-decoration: underline;" onclick="copyToClipboard(this, '{{ tx.hash }}','Transaction hash')">{{ tx.hash[:6] }}...{{ tx.hash[-6:] }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text">Nullam id dolor id nibh ultricies vehicula ut id elit. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus.</p>
|
||||
<h3>Inputs & Outputs</h3>
|
||||
<div class="card bg-dark-subtle" style="margin: 10px;">
|
||||
<div class="card-body">
|
||||
<div class="container" style="border-right-width: 1px;">
|
||||
<div class="row">
|
||||
<div class="col-md-6" style="border-right-width: 1px;border-right-style: solid;">{% for input in tx.inputs %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">{% if input.address %}
|
||||
<a href="/address/{{input.address}}">{{ input.address }}</a>
|
||||
{% elif input.coin %}
|
||||
<a href="/address/{{input.coin.address}}">{{ input.coin.address }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">The Void</span>
|
||||
{% endif %}</div>
|
||||
<div class="col">{% if input.address %}
|
||||
|
||||
{% elif input.coin %}
|
||||
<span>{{ "{:,.2f}".format(input.coin.value/1000000) }} HNS</span>
|
||||
{% if input.coin.covenant %}
|
||||
{% if input.coin.covenant.type != 0 %}
|
||||
{{ input.coin.covenant }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- Must be a coinbase -->
|
||||
|
||||
{% if input.witness %}
|
||||
{% if input.witness | length == 1 %}
|
||||
<span>Airdrop/Name Claim</span>
|
||||
<!-- {{input}} -->
|
||||
{% else %}
|
||||
<span>Mining Reward `{{ input.witness[0] | hexToAscii }}`</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-6">{% for output in tx.outputs %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">{% if output.address %}
|
||||
<a href="/address/{{output.address}}">{{ output.address }}</a>
|
||||
{% elif output.coin %}
|
||||
<a href="/address/{{output.coin.address}}">{{ output.coin.address }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">The Void</span>
|
||||
{% endif %}</div>
|
||||
<div class="col text-end">{% if output.covenant %}
|
||||
{% if output.covenant.action == "NONE" %}
|
||||
{{"{:,.2f}".format(output.value / 1000000) }} HNS
|
||||
{% else %}
|
||||
{{ output.covenant | parse_covenant }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{"{:,.2f}".format(output.value / 1000000) }} HNS
|
||||
{% endif %}</div>
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">Services</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
|
||||
<li><a href="https://nathan.woodburn.au/projects" target="_blank">Development</a></li>
|
||||
<li><a href="https://hnshosting.au" target="_blank">Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">About</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://nathan.woodburn.au/" target="_blank">Nathan.Woodburn/</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
|
||||
<div class="fw-bold d-flex align-items-center mb-2"><span>FireExplorer</span></div>
|
||||
<p class="text-muted">The piping hot Handshake Explorer by Nathan.Woodburn/</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © 2025 FireExplorer</p>
|
||||
<ul class="list-inline mb-0">
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-facebook">
|
||||
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-twitter">
|
||||
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15"></path>
|
||||
</svg></li>
|
||||
<li class="list-inline-item"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-instagram">
|
||||
<path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334"></path>
|
||||
</svg></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bold-and-dark.js"></script>
|
||||
<script src="/assets/js/copy.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|