1 Commits

Author SHA1 Message Date
0fc95d0eb1 feat: Start on packaging for windows
All checks were successful
Build Docker / Build Image (push) Successful in 44s
2025-02-05 13:03:57 +11:00
16 changed files with 333 additions and 332 deletions

2
.gitignore vendored
View File

@@ -14,5 +14,5 @@ plugins/signatures.json
user_data/ user_data/
customPlugins/ customPlugins/
cache/ cache/
build/
dist/ dist/
build/

Binary file not shown.

View File

@@ -122,7 +122,7 @@ HSD_IP: HSD IP address
THEME: Theme to use (dark-purple, black) THEME: Theme to use (dark-purple, black)
SHOW_EXPIRED: Show expired domains (true/false) SHOW_EXPIRED: Show expired domains (true/false)
EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary) EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary)
EXPLORER_TX: URL for exploring transactions (default https://shakeshift.com/transaction/) EXPLORER_TX: URL for exploring transactions (default https://niami.io/tx/)
HSD_NETWORK: Network to connect to (main, regtest, simnet) HSD_NETWORK: Network to connect to (main, regtest, simnet)
``` ```

File diff suppressed because it is too large Load Diff

View File

@@ -2,4 +2,4 @@ HSD_API=123480615465636893475aCwyaae6s45
HSD_IP=localhost HSD_IP=localhost
THEME=black THEME=black
SHOW_EXPIRED=false SHOW_EXPIRED=false
EXPLORER_TX=https://shakeshift.com/transaction/ EXPLORER_TX=https://niami.io/tx/

36
main.py
View File

@@ -178,15 +178,7 @@ def sendConfirmed():
address = request.args.get("address") address = request.args.get("address")
amount = float(request.args.get("amount")) amount = float(request.args.get("amount"))
response = account_module.send(request.cookies.get("account"),address,amount) response = account_module.send(request.cookies.get("account"),address,amount)
if 'error' in response and response['error'] != None: if 'error' in response:
# If error is a dict get the message
if isinstance(response['error'], dict):
if 'message' in response['error']:
return redirect("/send?message=" + response['error']['message'] + "&address=" + address + "&amount=" + str(amount))
else:
return redirect("/send?message=" + str(response['error']) + "&address=" + address + "&amount=" + str(amount))
# If error is a string
return redirect("/send?message=" + response['error'] + "&address=" + address + "&amount=" + str(amount)) return redirect("/send?message=" + response['error'] + "&address=" + address + "&amount=" + str(amount))
return redirect("/success?tx=" + response['tx']) return redirect("/success?tx=" + response['tx'])
@@ -559,6 +551,7 @@ def finalize(domain: str):
return redirect("/logout") return redirect("/logout")
domain = domain.lower() domain = domain.lower()
print(domain)
response = account_module.finalize(request.cookies.get("account"),domain) response = account_module.finalize(request.cookies.get("account"),domain)
if response['error'] != None: if response['error'] != None:
print(response) print(response)
@@ -577,6 +570,7 @@ def cancelTransfer(domain: str):
return redirect("/logout") return redirect("/logout")
domain = domain.lower() domain = domain.lower()
print(domain)
response = account_module.cancelTransfer(request.cookies.get("account"),domain) response = account_module.cancelTransfer(request.cookies.get("account"),domain)
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] != None:
@@ -891,6 +885,7 @@ def auction(domain):
# Get TX # Get TX
revealInfo = account_module.getRevealTX(reveal) revealInfo = account_module.getRevealTX(reveal)
reveal['bid'] = revealInfo reveal['bid'] = revealInfo
print(revealInfo)
bids = render.bids(bids,reveals) bids = render.bids(bids,reveals)
@@ -952,6 +947,7 @@ def rescan_auction(domain):
domain = domain.lower() domain = domain.lower()
response = account_module.rescan_auction(account,domain) response = account_module.rescan_auction(account,domain)
print(response)
return redirect("/auction/" + domain) return redirect("/auction/" + domain)
@app.route('/auction/<domain>/bid') @app.route('/auction/<domain>/bid')
@@ -1027,7 +1023,7 @@ def bid_confirm(domain):
response = account_module.bid(request.cookies.get("account"),domain, response = account_module.bid(request.cookies.get("account"),domain,
float(bid), float(bid),
float(blind)) float(blind))
print(response)
if 'error' in response: if 'error' in response:
return redirect("/auction/" + domain + "?error=" + response['error']['message']) return redirect("/auction/" + domain + "?error=" + response['error']['message'])
@@ -1049,7 +1045,7 @@ def open_auction(domain):
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] != None:
return redirect("/auction/" + domain + "?error=" + response['error']['message']) return redirect("/auction/" + domain + "?error=" + response['error']['message'])
print(response)
return redirect("/success?tx=" + response['hash']) return redirect("/success?tx=" + response['hash'])
@app.route('/auction/<domain>/reveal') @app.route('/auction/<domain>/reveal')
@@ -1116,7 +1112,8 @@ def settings():
# import to time from format "2024-02-13 11:24:03" # import to time from format "2024-02-13 11:24:03"
last_commit = datetime.datetime.strptime(last_commit, "%Y-%m-%d %H:%M:%S") last_commit = datetime.datetime.strptime(last_commit, "%Y-%m-%d %H:%M:%S")
version = f'{last_commit.strftime("%y-%m-%d")} {branch}' version = f'{last_commit.strftime("%y-%m-%d")} {branch}'
if info['commit'] != latestVersion(info['refs']):
if info['commit'] != latestVersion(branch):
version += ' (New version available)' version += ' (New version available)'
return render_template("settings.html", account=account, return render_template("settings.html", account=account,
hsd_version=account_module.hsdVersion(False), hsd_version=account_module.hsdVersion(False),
@@ -1223,7 +1220,8 @@ def login_post():
if account.count(":") > 0: if account.count(":") > 0:
wallets = account_module.listWallets() wallets = account_module.listWallets()
wallets = render.wallets(wallets) wallets = render.wallets(wallets)
return render_template("login.html", return render_template("login.html",
error="Invalid account",wallets=wallets) error="Invalid account",wallets=wallets)
account = account + ":" + password account = account + ":" + password
@@ -1596,6 +1594,8 @@ def try_path(path):
if not account_module.hsdConnected(): if not account_module.hsdConnected():
return redirect("/login?message=Node not connected") return redirect("/login?message=Node not connected")
if os.path.isfile("templates/" + path + ".html"): if os.path.isfile("templates/" + path + ".html"):
return render_template(path + ".html") return render_template(path + ".html")
else: else:
@@ -1610,9 +1610,7 @@ def page_not_found(e):
if __name__ == '__main__': if __name__ == '__main__':
# Check to see if --debug is in the command line arguments # Check to see if --debug is in the command line arguments
debug = "--debug" in sys.argv if "--debug" in sys.argv:
port = 5000 app.run(debug=True,host='0.0.0.0')
if "--port" in sys.argv: else:
port = int(sys.argv[sys.argv.index("--port")+1]) app.run(host='0.0.0.0',threaded=True)
app.run(debug=True,host='0.0.0.0',port=port)

63
main.spec Normal file
View File

@@ -0,0 +1,63 @@
# -*- mode: python ; coding: utf-8 -*-
import compileall
from PyInstaller.utils.hooks import collect_data_files
import os
compileall.compile_dir('.', force=True)
datas = [
('templates', 'templates'),
('assets', 'assets'),
('themes', 'themes'),
('plugins', 'plugins')
]
hiddenimports = [
'plugins.automations',
'plugins.batching',
'plugins.customPlugins',
'plugins.renewal',
'plugins.varo'
]
# Copy the plugins folder to the dist folder
os.system(f'cp -r plugins dist/')
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='FireWallet',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
onefile=False,
)

View File

@@ -9,7 +9,7 @@ import os
info = { info = {
"name": "Batching Functions", "name": "Batching Functions",
"description": "This is a plugin that provides multiple functions to batch transactions", "description": "This is a plugin that provides multiple functions to batch transactions",
"version": "1.1", "version": "1.0",
"author": "Nathan.Woodburn/" "author": "Nathan.Woodburn/"
} }
# https://hsd-dev.org/api-docs/?shell--cli#sendbatch # https://hsd-dev.org/api-docs/?shell--cli#sendbatch
@@ -394,6 +394,7 @@ def bid(params, authentication):
for domain in domains: for domain in domains:
batch.append(['BID', domain, bid, blind]) batch.append(['BID', domain, bid, blind])
print(batch)
response = sendBatch(batch, authentication) response = sendBatch(batch, authentication)
if 'error' in response: if 'error' in response:
return { return {

View File

@@ -8,7 +8,7 @@ import os
# Get Explorer URL # Get Explorer URL
TX_EXPLORER_URL = os.getenv("EXPLORER_TX") TX_EXPLORER_URL = os.getenv("EXPLORER_TX")
if TX_EXPLORER_URL is None: if TX_EXPLORER_URL is None:
TX_EXPLORER_URL = "https://shakeshift.com/transaction/" TX_EXPLORER_URL = "https://niami.io/tx/"

View File

@@ -8,5 +8,4 @@ cryptography
requests-doh requests-doh
Flask-QRcode Flask-QRcode
PySocks PySocks
python-git-info python-git-info
waitress

View File

@@ -1,44 +1,38 @@
import os from flask import Flask
import sys
import platform
from main import app from main import app
from waitress import serve import main
from gunicorn.app.base import BaseApplication
import os
threads = 4 class GunicornApp(BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def gunicornServer(): def load_config(self):
from gunicorn.app.base import BaseApplication for key, value in self.options.items():
class GunicornApp(BaseApplication): if key in self.cfg.settings and value is not None:
def __init__(self, app, options=None): self.cfg.set(key.lower(), value)
self.options = options or {}
self.application = app
super().__init__()
def load_config(self): def load(self):
for key, value in self.options.items(): return self.application
if key in self.cfg.settings and value is not None:
self.cfg.set(key.lower(), value)
def load(self): if __name__ == '__main__':
return self.application workers = 1
threads = 2
if workers is None:
workers = 1
if threads is None:
threads = 2
workers = int(workers)
threads = int(threads)
options = { options = {
'bind': '0.0.0.0:5000', 'bind': '0.0.0.0:5000',
'workers': 2, 'workers': workers,
'threads': threads, 'threads': threads,
} }
gunicorn_app = GunicornApp(app, options) gunicorn_app = GunicornApp(app, options)
print(f'Starting server with Gunicorn on {platform.system()} with {threads} threads...', flush=True) print('Starting server with ' + str(workers) + ' workers and ' + str(threads) + ' threads', flush=True)
gunicorn_app.run() gunicorn_app.run()
if __name__ == '__main__':
# Check if --gunicorn is in the command line arguments
if "--gunicorn" in sys.argv:
gunicornServer()
sys.exit()
print(f'Starting server with Waitress on {platform.system()} with {threads} threads...', flush=True)
print(f'Press Ctrl+C to stop the server', flush=True)
print(f'Serving on http://0.0.0.0:5000/', flush=True)
serve(app, host="0.0.0.0", port=5000, threads=threads)

View File

@@ -1 +1 @@
async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const n=await t.json();return void 0!==n.error?`Error: ${n.error}`:n.result}catch(e){return console.error("Request failed:",e),"Error"}}function sortTable(e,t=!1){const n=document.getElementById("data-table"),a=n.querySelector("tbody"),l=Array.from(a.querySelectorAll("tr")),r=n.querySelectorAll("th");let o=n.getAttribute("data-sort-order")||"asc",i=n.getAttribute("data-sort-column")||"-1";o=t||i!=e?"asc":"asc"===o?"desc":"asc",n.setAttribute("data-sort-order",o),n.setAttribute("data-sort-column",e);const c=determineColumnDataType(l,e);l.sort(((t,n)=>{let a=t.cells[e].innerText.trim(),l=n.cells[e].innerText.trim();if("number"===c){let e=parseFloat(a.replace(/[^0-9.,]/g,"").replace(/,/g,"")),t=parseFloat(l.replace(/[^0-9.,]/g,"").replace(/,/g,""));return"asc"===o?e-t:t-e}if("date"===c){let e=new Date(a),t=new Date(l);return"asc"===o?e-t:t-e}return"asc"===o?a.localeCompare(l,void 0,{sensitivity:"base"}):l.localeCompare(a,void 0,{sensitivity:"base"})})),a.innerHTML="",l.forEach((e=>a.appendChild(e))),updateSortIndicators(r,e,o)}function determineColumnDataType(e,t){const n=Math.min(5,e.length);let a=0,l=0;for(let r=0;r<n&&!(r>=e.length);r++){const n=e[r].cells[t].innerText.trim(),o=parseFloat(n.replace(/[^0-9.,]/g,"").replace(/,/g,""));if(!isNaN(o)&&n.replace(/[^0-9.,\s$%]/g,"").length===n.length){a++;continue}const i=new Date(n);isNaN(i)||"Invalid Date"===i.toString()||l++}return a>=n/2?"number":l>=n/2?"date":"text"}function updateSortIndicators(e,t,n){e.forEach(((e,a)=>{let l=e.querySelector(".sort-indicator");l.innerHTML=a===t?"asc"===n?" ▲":" ▼":""}))}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked"],n=["wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"];for(const a of e){const e=document.getElementById(a);if(e){const l=a.replace(/-/g,"/");let r=await request(l);n.includes(a)&&"Error"!=r&&(r=r.length),t.includes(a)&&(r=Number(r).toFixed(2)),r=r.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=r}}})),document.addEventListener("DOMContentLoaded",(function(){fetch("/api/v1/wallet/domains").then((e=>e.json())).then((e=>{const t=document.querySelector("#data-table tbody");t&&(t.innerHTML="",e.result.forEach((e=>{const n=document.createElement("tr"),a=document.createElement("td");a.textContent=e.name,n.appendChild(a);var l="Unknown";"stats"in e&&"daysUntilExpire"in e.stats&&(l=e.stats.daysUntilExpire);const r=document.createElement("td");r.textContent=`${l} days`,n.appendChild(r);const o=document.createElement("td");o.textContent=`${(e.value/1e6).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")} HNS`,n.appendChild(o);const i=document.createElement("td");i.innerHTML=e.registered?"<a href='/manage/"+e.name+"'>Manage</a>":"<a href='/auction/"+e.name+"/register'>Register</a>",n.appendChild(i),t.appendChild(n)})),sortTable(0,!0))})).catch((e=>console.error("Error fetching data:",e)))})),setInterval((async function(){const e=["hsd-sync","hsd-height","wallet-sync","wallet-pending","wallet-available","wallet-total"];for(const t of e){const e=document.getElementById(t);if(e){const n=t.replace(/-/g,"/");let a=await request(n);["wallet-available","wallet-total"].includes(t)&&(a=Number(a).toFixed(2)),a=a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=a}}}),2e4),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var n=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var a of t)a.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var a of n)a.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of n)e.hide()}))}var l=document.querySelector("body.fixed-nav .sidebar");l&&l.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,n=t.wheelDelta||-t.detail;this.scrollTop+=30*(n<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}(); async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const n=await t.json();return void 0!==n.error?`Error: ${n.error}`:n.result}catch(e){return console.error("Request failed:",e),"Error"}}function sortTable(e,t=!1){const n=document.getElementById("data-table"),a=n.querySelector("tbody"),l=Array.from(a.querySelectorAll("tr")),r=n.querySelectorAll("th");let o=n.getAttribute("data-sort-order")||"asc",d=n.getAttribute("data-sort-column")||"-1";o=t||d!=e?"asc":"asc"===o?"desc":"asc",n.setAttribute("data-sort-order",o),n.setAttribute("data-sort-column",e),l.sort(((t,n)=>{let a=t.cells[e].innerText.trim(),l=n.cells[e].innerText.trim(),r=parseFloat(a.replace(/[^0-9.,]/g,"").replace(/,/g,"")),d=parseFloat(l.replace(/[^0-9.,]/g,"").replace(/,/g,""));return isNaN(r)||isNaN(d)?"asc"===o?a.localeCompare(l):l.localeCompare(a):"asc"===o?r-d:d-r})),a.innerHTML="",l.forEach((e=>a.appendChild(e))),updateSortIndicators(r,e,o)}function updateSortIndicators(e,t,n){e.forEach(((e,a)=>{let l=e.querySelector(".sort-indicator");l.innerHTML=a===t?"asc"===n?" ▲":" ▼":""}))}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked"],n=["wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"];for(const a of e){const e=document.getElementById(a);if(e){const l=a.replace(/-/g,"/");let r=await request(l);n.includes(a)&&"Error"!=r&&(r=r.length),t.includes(a)&&(r=Number(r).toFixed(2)),r=r.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=r}}})),document.addEventListener("DOMContentLoaded",(function(){fetch("/api/v1/wallet/domains").then((e=>e.json())).then((e=>{const t=document.querySelector("#data-table tbody");t&&(t.innerHTML="",e.result.forEach((e=>{const n=document.createElement("tr"),a=document.createElement("td");a.textContent=e.name,n.appendChild(a);var l="Unknown";"stats"in e&&"daysUntilExpire"in e.stats&&(l=e.stats.daysUntilExpire);const r=document.createElement("td");r.textContent=`${l} days`,n.appendChild(r);const o=document.createElement("td");o.textContent=`${(e.value/1e6).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")} HNS`,n.appendChild(o);const d=document.createElement("td");d.innerHTML=e.registered?"<a href='/manage/"+e.name+"'>Manage</a>":"<a href='/auction/"+e.name+"/register'>Register</a>",n.appendChild(d),t.appendChild(n)})),sortTable(0,!0))})).catch((e=>console.error("Error fetching data:",e)))})),setInterval((async function(){const e=["hsd-sync","hsd-height","wallet-sync","wallet-pending","wallet-available","wallet-total"];for(const t of e){const e=document.getElementById(t);if(e){const n=t.replace(/-/g,"/");let a=await request(n);["wallet-available","wallet-total"].includes(t)&&(a=Number(a).toFixed(2)),a=a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=a}}}),2e4),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var n=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var a of t)a.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var a of n)a.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of n)e.hide()}))}var l=document.querySelector("body.fixed-nav .sidebar");l&&l.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,n=t.wheelDelta||-t.detail;this.scrollTop+=30*(n<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}();

View File

@@ -2,4 +2,4 @@
<span style="display: block;">Check your transaction on a block explorer</span> <span style="display: block;">Check your transaction on a block explorer</span>
<a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a> <a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a>
<a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a> <a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a>
<a class="card-link" href="https://shakeshift.com/transaction/{{tx}}" target="_blank">ShakeShift</a> <a class="card-link" href="https://hns.cymon.de/tx/{{tx}}" target="_blank">Cymon.de</a>

View File

@@ -74,8 +74,7 @@
<div class="container-fluid" style="margin-top: 50px;"> <div class="container-fluid" style="margin-top: 50px;">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" style="display: inline-block;">DNS</h4> <h4 class="card-title" style="display: inline-block;">DNS</h4><a class="btn btn-primary" role="button" style="position: absolute; right:16px;" href="/manage/{{domain}}/edit?dns={{raw_dns}}">Edit</a><div class="table-responsive">
<div style="width: fit-content;position: absolute;right: 0px;top: 16px;"><a class="btn btn-primary" role="button" href="https://tools.c.woodburn.au/?domain={{domain}}&amp;url=https://{{domain}}" style="margin: 0px 16px;" target="_blank">Debug</a><a class="btn btn-primary" role="button" href="/manage/{{domain}}/edit?dns={{raw_dns}}" style="margin: 0px 16px;">Edit</a></div><div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>

View File

@@ -64,7 +64,7 @@
</nav> </nav>
<div class="container-fluid" style="margin-bottom: 20px;"> <div class="container-fluid" style="margin-bottom: 20px;">
<h3 class="text-dark mb-1">{{name}}</h3> <h3 class="text-dark mb-1">{{name}}</h3>
<h4 class="text-dark mb-1">{{description|safe}}</h4>{{output|safe}} <h4 class="text-dark mb-1">{{description}}</h4>{{output|safe}}
</div> </div>
</div> </div>
<footer class="sticky-footer" style="background: var(--bs-primary-text-emphasis);"> <footer class="sticky-footer" style="background: var(--bs-primary-text-emphasis);">

View File

@@ -67,7 +67,7 @@
</div> </div>
<div class="card" style="max-width: 500px;margin: auto;margin-top: 50px;"> <div class="card" style="max-width: 500px;margin: auto;margin-top: 50px;">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">Your transaction has been sent and will be mined soon.</h4><span style="display: block;font-size: 12px;">TX: {{tx}}</span><span style="display: block;">Check your transaction on a block explorer</span><a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a><a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a><a class="card-link" href="https://shakeshift.com/transaction/{{tx}}" target="_blank">ShakeShift</a> <h4 class="card-title">Your transaction has been sent and will be mined soon.</h4><span style="display: block;font-size: 12px;">TX: {{tx}}</span><span style="display: block;">Check your transaction on a block explorer</span><a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a><a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a><a class="card-link" href="https://hns.cymon.de/tx/{{tx}}" target="_blank">HNS.Cymon.de</a>
</div> </div>
</div> </div>
</div> </div>