feat: Add initial internal node option
All checks were successful
Build Docker / Build Image (push) Successful in 10m29s
All checks were successful
Build Docker / Build Image (push) Successful in 10m29s
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,3 +17,5 @@ cache/
|
|||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
hsd/
|
hsd/
|
||||||
|
hsd-data/
|
||||||
|
hsd.lock
|
||||||
|
|||||||
Binary file not shown.
@@ -124,6 +124,8 @@ 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://shakeshift.com/transaction/)
|
||||||
HSD_NETWORK: Network to connect to (main, regtest, simnet)
|
HSD_NETWORK: Network to connect to (main, regtest, simnet)
|
||||||
|
DISABLE_WALLETDNS: Disable Wallet DNS records when sending HNS to domains (true/false)
|
||||||
|
INTERNAL_HSD: Use internal HSD node (true/false)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
213
account.py
213
account.py
@@ -7,6 +7,11 @@ import re
|
|||||||
import domainLookup
|
import domainLookup
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
|
import atexit
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
@@ -29,17 +34,33 @@ elif HSD_NETWORK == "regtest":
|
|||||||
HSD_WALLET_PORT = 14039
|
HSD_WALLET_PORT = 14039
|
||||||
HSD_NODE_PORT = 14037
|
HSD_NODE_PORT = 14037
|
||||||
|
|
||||||
INTERNAL_NODE = os.getenv("INTERNAL_HSD","false").lower() in ["1","true","yes"]
|
HSD_INTERNAL_NODE = os.getenv("INTERNAL_HSD","false").lower() in ["1","true","yes"]
|
||||||
if INTERNAL_NODE:
|
if HSD_INTERNAL_NODE:
|
||||||
if HSD_API == "":
|
if HSD_API == "":
|
||||||
# Use a random API KEY
|
# Use a random API KEY
|
||||||
HSD_API = "firewallet-" + str(int(time.time()))
|
HSD_API = "firewallet-" + str(int(time.time()))
|
||||||
|
HSD_IP = "localhost"
|
||||||
|
|
||||||
SHOW_EXPIRED = os.getenv("SHOW_EXPIRED")
|
SHOW_EXPIRED = os.getenv("SHOW_EXPIRED")
|
||||||
if SHOW_EXPIRED is None:
|
if SHOW_EXPIRED is None:
|
||||||
SHOW_EXPIRED = False
|
SHOW_EXPIRED = False
|
||||||
|
|
||||||
|
HSD_PROCESS = None
|
||||||
|
|
||||||
|
# Get hsdconfig.json
|
||||||
|
HSD_CONFIG = {}
|
||||||
|
if not os.path.exists('hsdconfig.json'):
|
||||||
|
# Pull from the latest git
|
||||||
|
response = requests.get("https://git.woodburn.au/nathanwoodburn/firewalletbrowser/raw/branch/main/hsdconfig.json")
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open('hsdconfig.json', 'w') as f:
|
||||||
|
f.write(response.text)
|
||||||
|
HSD_CONFIG = response.json()
|
||||||
|
else:
|
||||||
|
with open('hsdconfig.json') as f:
|
||||||
|
HSD_CONFIG = json.load(f)
|
||||||
|
|
||||||
|
|
||||||
hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
|
hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
|
||||||
hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT)
|
hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT)
|
||||||
|
|
||||||
@@ -1439,7 +1460,6 @@ def generateReport(account, format="{name},{expiry},{value},{maxBid}"):
|
|||||||
|
|
||||||
def convertHNS(value: int):
|
def convertHNS(value: int):
|
||||||
return value/1000000
|
return value/1000000
|
||||||
return value/1000000
|
|
||||||
|
|
||||||
|
|
||||||
def get_node_api_url(path=''):
|
def get_node_api_url(path=''):
|
||||||
@@ -1461,3 +1481,188 @@ def get_wallet_api_url(path=''):
|
|||||||
path = f'/{path}'
|
path = f'/{path}'
|
||||||
return f"{base_url}{path}"
|
return f"{base_url}{path}"
|
||||||
return base_url
|
return base_url
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# region HSD Internal Node
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def checkPreRequisites() -> dict[str, bool]:
|
||||||
|
prerequisites = {
|
||||||
|
"node": False,
|
||||||
|
"npm": False,
|
||||||
|
"git": False,
|
||||||
|
"hsd": False
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if node is installed and get version
|
||||||
|
nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True)
|
||||||
|
if nodeSubprocess.returncode == 0:
|
||||||
|
major_version = int(nodeSubprocess.stdout.strip().lstrip('v').split('.')[0])
|
||||||
|
if major_version >= HSD_CONFIG.get("minNodeVersion", 20):
|
||||||
|
prerequisites["node"] = True
|
||||||
|
|
||||||
|
# Check if npm is installed
|
||||||
|
npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True)
|
||||||
|
if npmSubprocess.returncode == 0:
|
||||||
|
major_version = int(npmSubprocess.stdout.strip().split('.')[0])
|
||||||
|
if major_version >= HSD_CONFIG.get("minNPMVersion", 8):
|
||||||
|
prerequisites["npm"] = True
|
||||||
|
|
||||||
|
# Check if git is installed
|
||||||
|
gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True)
|
||||||
|
if gitSubprocess.returncode == 0:
|
||||||
|
prerequisites["git"] = True
|
||||||
|
|
||||||
|
# Check if hsd is installed
|
||||||
|
if os.path.exists("./hsd/bin/hsd"):
|
||||||
|
prerequisites["hsd"] = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return prerequisites
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def hsdInit():
|
||||||
|
if not HSD_INTERNAL_NODE:
|
||||||
|
return
|
||||||
|
prerequisites = checkPreRequisites()
|
||||||
|
|
||||||
|
PREREQ_MESSAGES = {
|
||||||
|
"node": "Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})",
|
||||||
|
"npm": "Install npm (version >= {minNPMVersion}) - usually comes with Node.js",
|
||||||
|
"git": "Install Git from https://git-scm.com/downloads"}
|
||||||
|
|
||||||
|
|
||||||
|
# Check if all prerequisites are met (except hsd)
|
||||||
|
if not all(prerequisites[key] for key in prerequisites if key != "hsd"):
|
||||||
|
print("HSD Internal Node prerequisites not met:")
|
||||||
|
for key, value in prerequisites.items():
|
||||||
|
if not value:
|
||||||
|
print(f" - {key} is missing or does not meet the version requirement.")
|
||||||
|
exit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if hsd is installed
|
||||||
|
if not prerequisites["hsd"]:
|
||||||
|
print("HSD not found, installing...")
|
||||||
|
# If hsd folder exists, remove it
|
||||||
|
if os.path.exists("hsd"):
|
||||||
|
os.rmdir("hsd")
|
||||||
|
|
||||||
|
# Clone hsd repo
|
||||||
|
gitClone = subprocess.run(["git", "clone", "--depth", "1", "--branch", HSD_CONFIG.get("version", "latest"), "https://github.com/handshake-org/hsd.git", "hsd"], capture_output=True, text=True)
|
||||||
|
if gitClone.returncode != 0:
|
||||||
|
print("Failed to clone hsd repository:")
|
||||||
|
print(gitClone.stderr)
|
||||||
|
exit(1)
|
||||||
|
print("Cloned hsd repository.")
|
||||||
|
# Install hsd dependencies
|
||||||
|
print("Installing hsd dependencies...")
|
||||||
|
npmInstall = subprocess.run(["npm", "install"], cwd="hsd", capture_output=True, text=True)
|
||||||
|
if npmInstall.returncode != 0:
|
||||||
|
print("Failed to install hsd dependencies:")
|
||||||
|
print(npmInstall.stderr)
|
||||||
|
exit(1)
|
||||||
|
print("Installed hsd dependencies.")
|
||||||
|
|
||||||
|
def hsdStart():
|
||||||
|
global HSD_PROCESS
|
||||||
|
if not HSD_INTERNAL_NODE:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if hsd was started in the last 30 seconds
|
||||||
|
if os.path.exists("hsd.lock"):
|
||||||
|
lock_time = os.path.getmtime("hsd.lock")
|
||||||
|
if time.time() - lock_time < 30:
|
||||||
|
print("HSD was started recently, skipping start.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
os.remove("hsd.lock")
|
||||||
|
|
||||||
|
print("Starting HSD...")
|
||||||
|
# Create a lock file
|
||||||
|
with open("hsd.lock", "w") as f:
|
||||||
|
f.write(str(time.time()))
|
||||||
|
|
||||||
|
# Config lookups with defaults
|
||||||
|
chain_migrate = HSD_CONFIG.get("chainMigrate", False)
|
||||||
|
wallet_migrate = HSD_CONFIG.get("walletMigrate", False)
|
||||||
|
spv = HSD_CONFIG.get("spv", False)
|
||||||
|
|
||||||
|
# Base command
|
||||||
|
cmd = [
|
||||||
|
"node",
|
||||||
|
"./hsd/bin/hsd",
|
||||||
|
f"--network={HSD_NETWORK}",
|
||||||
|
f"--prefix={os.path.join(os.getcwd(), 'hsd-data')}",
|
||||||
|
f"--api-key={HSD_API}",
|
||||||
|
"--agent=FireWallet",
|
||||||
|
"--http-host=127.0.0.1",
|
||||||
|
"--log-console=false"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Conditionally add migration flags
|
||||||
|
if chain_migrate:
|
||||||
|
cmd.append(f"--chain-migrate={chain_migrate}")
|
||||||
|
if wallet_migrate:
|
||||||
|
cmd.append(f"--wallet-migrate={wallet_migrate}")
|
||||||
|
if spv:
|
||||||
|
cmd.append("--spv")
|
||||||
|
|
||||||
|
|
||||||
|
# Launch process
|
||||||
|
HSD_PROCESS = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
cwd=os.getcwd(),
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"HSD started with PID {HSD_PROCESS.pid}")
|
||||||
|
|
||||||
|
atexit.register(hsdStop)
|
||||||
|
|
||||||
|
# Handle Ctrl+C
|
||||||
|
try:
|
||||||
|
signal.signal(signal.SIGINT, lambda s, f: (hsdStop(), sys.exit(0)))
|
||||||
|
signal.signal(signal.SIGTERM, lambda s, f: (hsdStop(), sys.exit(0)))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def hsdStop():
|
||||||
|
global HSD_PROCESS
|
||||||
|
|
||||||
|
if HSD_PROCESS is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Stopping HSD...")
|
||||||
|
|
||||||
|
# Send SIGINT (like Ctrl+C)
|
||||||
|
HSD_PROCESS.send_signal(signal.SIGINT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
HSD_PROCESS.wait(timeout=10) # wait for graceful exit
|
||||||
|
print("HSD shut down cleanly.")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print("HSD did not exit yet, is it alright???")
|
||||||
|
|
||||||
|
# Clean up lock file
|
||||||
|
if os.path.exists("hsd.lock"):
|
||||||
|
os.remove("hsd.lock")
|
||||||
|
|
||||||
|
HSD_PROCESS = None
|
||||||
|
|
||||||
|
def hsdRestart():
|
||||||
|
hsdStop()
|
||||||
|
time.sleep(2)
|
||||||
|
hsdStart()
|
||||||
|
|
||||||
|
|
||||||
|
checkPreRequisites()
|
||||||
|
hsdInit()
|
||||||
|
hsdStart()
|
||||||
|
# endregion
|
||||||
8
hsdconfig.json
Normal file
8
hsdconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": "v8.0.0",
|
||||||
|
"chainMigrate":4,
|
||||||
|
"walletMigrate":7,
|
||||||
|
"minNodeVersion":20,
|
||||||
|
"minNpmVersion":8,
|
||||||
|
"spv": true
|
||||||
|
}
|
||||||
24
main.py
24
main.py
@@ -1150,14 +1150,13 @@ def settings():
|
|||||||
|
|
||||||
if not os.path.exists(".git"):
|
if not os.path.exists(".git"):
|
||||||
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),
|
||||||
error=error,success=success,version="Error")
|
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE)
|
||||||
info = gitinfo.get_git_info()
|
info = gitinfo.get_git_info()
|
||||||
if not info:
|
if not info:
|
||||||
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),
|
||||||
error=error,success=success,version="Error")
|
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE)
|
||||||
|
|
||||||
branch = info['refs']
|
branch = info['refs']
|
||||||
if branch != "main":
|
if branch != "main":
|
||||||
@@ -1172,7 +1171,7 @@ def settings():
|
|||||||
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),
|
||||||
error=error,success=success,version=version)
|
error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE)
|
||||||
|
|
||||||
@app.route('/settings/<action>')
|
@app.route('/settings/<action>')
|
||||||
def settings_action(action):
|
def settings_action(action):
|
||||||
@@ -1189,19 +1188,21 @@ def settings_action(action):
|
|||||||
if 'error' in resp:
|
if 'error' in resp:
|
||||||
return redirect("/settings?error=" + str(resp['error']))
|
return redirect("/settings?error=" + str(resp['error']))
|
||||||
return redirect("/settings?success=Rescan started")
|
return redirect("/settings?success=Rescan started")
|
||||||
elif action == "resend":
|
|
||||||
|
if action == "resend":
|
||||||
resp = account_module.resendTXs()
|
resp = account_module.resendTXs()
|
||||||
if 'error' in resp:
|
if 'error' in resp:
|
||||||
return redirect("/settings?error=" + str(resp['error']))
|
return redirect("/settings?error=" + str(resp['error']))
|
||||||
return redirect("/settings?success=Resent transactions")
|
return redirect("/settings?success=Resent transactions")
|
||||||
|
|
||||||
|
|
||||||
elif action == "zap":
|
if action == "zap":
|
||||||
resp = account_module.zapTXs(request.cookies.get("account"))
|
resp = account_module.zapTXs(request.cookies.get("account"))
|
||||||
if type(resp) == dict and 'error' in resp:
|
if type(resp) == dict and 'error' in resp:
|
||||||
return redirect("/settings?error=" + str(resp['error']))
|
return redirect("/settings?error=" + str(resp['error']))
|
||||||
return redirect("/settings?success=Zapped transactions")
|
return redirect("/settings?success=Zapped transactions")
|
||||||
elif action == "xpub":
|
|
||||||
|
if action == "xpub":
|
||||||
xpub = account_module.getxPub(request.cookies.get("account"))
|
xpub = account_module.getxPub(request.cookies.get("account"))
|
||||||
content = "<br><br>"
|
content = "<br><br>"
|
||||||
content += f"<textarea style='display: none;' id='data' rows='4' cols='50'>{xpub}</textarea>"
|
content += f"<textarea style='display: none;' id='data' rows='4' cols='50'>{xpub}</textarea>"
|
||||||
@@ -1212,6 +1213,12 @@ def settings_action(action):
|
|||||||
title="xPub Key",
|
title="xPub Key",
|
||||||
content=f"<code>{xpub}</code>{content}")
|
content=f"<code>{xpub}</code>{content}")
|
||||||
|
|
||||||
|
if action == "restart":
|
||||||
|
resp = account_module.hsdRestart()
|
||||||
|
return render_template("message.html", account=account,
|
||||||
|
title="Restarting",
|
||||||
|
content="The node is restarting. This may take a minute or two. You can close this window.")
|
||||||
|
|
||||||
return redirect("/settings?error=Invalid action")
|
return redirect("/settings?error=Invalid action")
|
||||||
|
|
||||||
@app.route('/settings/upload', methods=['POST'])
|
@app.route('/settings/upload', methods=['POST'])
|
||||||
@@ -1259,6 +1266,9 @@ def login():
|
|||||||
wallets = account_module.listWallets()
|
wallets = account_module.listWallets()
|
||||||
wallets = render.wallets(wallets)
|
wallets = render.wallets(wallets)
|
||||||
|
|
||||||
|
# If there are no wallets redirect to either register or import
|
||||||
|
if len(wallets) == 0:
|
||||||
|
return redirect("/welcome")
|
||||||
|
|
||||||
if 'message' in request.args:
|
if 'message' in request.args:
|
||||||
return render_template("login.html",
|
return render_template("login.html",
|
||||||
|
|||||||
@@ -69,24 +69,30 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">Node Settings</h4><small>HSD Version: v{{hsd_version}}</small>
|
<h4 class="card-title">Node Settings</h4><small>HSD Version: v{{hsd_version}}</small>
|
||||||
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6>
|
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6><ul class="list-group">
|
||||||
<ul class="list-group">
|
<li class="list-group-item">
|
||||||
<li class="list-group-item">
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a>
|
||||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a>
|
<h3>Rescan</h3><span>Rescan the blockchain for transactions</span>
|
||||||
<h3>Rescan</h3><span>Rescan the blockchain for transactions</span>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
<li class="list-group-item">
|
||||||
<li class="list-group-item">
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/resend">Resend</a>
|
||||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/resend">Resend</a>
|
<h3>Resend unconfirmed transactions</h3><span>Resend any transactions that haven't been mined yet.</span>
|
||||||
<h3>Resend unconfirmed transactions</h3><span>Resend any transactions that haven't been mined yet.</span>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
<li class="list-group-item">
|
||||||
<li class="list-group-item">
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/zap">Zap</a>
|
||||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/zap">Zap</a>
|
<h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
|
||||||
<h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
{% if internal %}
|
||||||
</ul>
|
<li class="list-group-item">
|
||||||
|
<div><a class="btn btn-primary stick-right" role="button" href="/settings/restart">Restart Node</a>
|
||||||
|
<h3>Restart Internal Node</h3><span>This will attempt to restart the HSD node</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
47
templates/welcome.html
Normal file
47
templates/welcome.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="dark" lang="en-au" style="height: 100%;">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>Welcome to FireWallet</title>
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
<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=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i&display=swap">
|
||||||
|
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="d-flex align-items-center bg-gradient-primary" style="height: 100%;">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-9 col-lg-12 col-xl-10">
|
||||||
|
<h1 class="text-center" style="color: var(--bs-danger);background: var(--bs-primary);">{{error}}</h1>
|
||||||
|
<div class="card shadow-lg my-5 o-hidden border-0" style="padding-top: 50px;padding-bottom: 50px;">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 d-none d-lg-flex">
|
||||||
|
<div class="flex-grow-1 bg-login-image" style="background: url("/assets/img/favicon.png") center / contain no-repeat;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="text-center p-5">
|
||||||
|
<div class="text-center">
|
||||||
|
<h4 class="mb-4">Welcome to FireWallet!</h4>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group-vertical btn-group-lg gap-1" role="group"><a class="btn btn-primary" role="button" href="/register">Create a new wallet</a><a class="btn btn-primary" role="button" href="/import-wallet">Import an existing wallet</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="/assets/js/script.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user