theseus/bundled-addons/bchwallet/panel.html
Local Dev 0883a4842e feat(theseus/aegis): official brand favicon + discoverable Add-wallet UX
Two fixes off the first-launch feedback: users didn't see how to add a
wallet, and the branded shield from aegis.x/brand hadn't landed in the
panel.

- panel.html + panel.js: swap the ad-hoc shield SVG for the exact mark
  from aegis.x/brand/favicon.svg — hexagonal aspis with dark fill +
  acid stroke + boss ring + centre point. The panel's tab favicon
  (<link rel="icon">) and the "Aegis" fallback badge in the header
  now render byte-close to what a user downloads from the brand kit.
- Add-wallet discoverability: an always-visible "+" chip lives in the
  header next to the picker caret; clicking it opens the picker with
  the coin list pre-expanded. When the panel is genuinely empty (a
  vault Aegis hasn't seen before), the gate now shows a big primary
  "+ Add your first wallet" button plus copy that spells out the
  seed source — Aegis derives every wallet from the Theseus password
  vault, no separate seed to import.
- addon.json bumped to 0.3.1 so seedBundledAddons() picks up the fresh
  panel files on the next Theseus launch (bundleVer === userVer would
  otherwise skip the reseed and users would keep loading the old
  panel from their addons/ dir).
2026-09-08 13:19:29 +02:00

374 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Aegis Wallet</title>
<!-- Aegis brand mark from aegis.x/brand/favicon.svg — kept byte-close so
the sidebar-tab icon reads as the identity a user just downloaded from. -->
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;utf8,%3Csvg%20xmlns%3D'http%3A//www.w3.org/2000/svg'%20viewBox%3D'0%200%2032%2032'%20fill%3D'none'%3E%3Cpolygon%20points%3D'16%2C2%2028%2C9%2028%2C23%2016%2C30%204%2C23%204%2C9'%20fill%3D'%230a0a0d'%20stroke%3D'%23D6FF3D'%20stroke-width%3D'1.6'%20stroke-linejoin%3D'round'/%3E%3Ccircle%20cx%3D'16'%20cy%3D'16'%20r%3D'4.5'%20fill%3D'none'%20stroke%3D'%23D6FF3D'%20stroke-width%3D'1.4'/%3E%3Ccircle%20cx%3D'16'%20cy%3D'16'%20r%3D'1.6'%20fill%3D'%23D6FF3D'/%3E%3C/svg%3E">
<style>
:root { color-scheme: light dark;
--bg:#0e131c; --panel:#141a24; --card:#0f1621; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d; --danger:#f6768a; --ok:#5ad38a;
--bch:#0ac18e; --trx:#ff060a; --trxNile:#4d9dff; }
@media (prefers-color-scheme: light) {
:root { --bg:#f8faff; --panel:#ffffff; --card:#f1f4fa; --line:rgba(0,0,0,.10);
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5;
/* Darker acid on light backgrounds — ~7:1 contrast on white. */
--acid: #0AC18E; }
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body { background: var(--bg); color: var(--ink);
font: 13.5px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
display: flex; flex-direction: column; }
header { padding: 10px 14px 10px; border-bottom: 1px solid var(--line); background: var(--panel); position: relative; }
.picker { display: flex; align-items: center; justify-content: space-between; gap: 8px; cursor: pointer; user-select: none; }
.picker .t { display: flex; align-items: center; gap: 7px; font-weight: 600; min-width: 0; }
.picker .t .badge { font-size: 15px; line-height: 1; }
.picker .t .lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker .caret { color: var(--mut); font-size: 10px; }
.picker:hover { color: var(--acid); }
.net { display: flex; align-items: center; gap: 6px; color: var(--dim); font-size: 11.5px; white-space: nowrap; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dim); }
.dot.on { background: var(--ok); } .dot.busy { background: #e0b341; }
.bal { margin-top: 8px; font-variant-numeric: tabular-nums; }
.bal .big { font-size: 22px; font-weight: 650; letter-spacing: .2px; }
.bal .big small { font-size: 13px; color: var(--mut); font-weight: 500; margin-left: 4px; }
.bal .sub { color: var(--dim); font-size: 11.5px; display: flex; justify-content: space-between; gap: 8px; }
.bal .sub .netlbl { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#drop { position: absolute; left: 10px; right: 10px; top: 100%; background: var(--panel); border: 1px solid var(--line);
border-radius: 10px; box-shadow: 0 8px 26px rgba(0,0,0,.3); z-index: 20; padding: 4px; margin-top: 4px; max-height: 60vh; overflow-y: auto; }
#drop[hidden] { display: none; }
#drop .row, #drop .coinrow { display: flex; align-items: center; gap: 9px; padding: 7px 8px; border-radius: 7px; cursor: pointer; }
#drop .row:hover, #drop .coinrow:hover { background: rgba(255,255,255,.05); }
#drop .row .m, #drop .coinrow .m { flex: 1; min-width: 0; }
#drop .row .m .l, #drop .coinrow .m .l { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#drop .row .m .s, #drop .coinrow .m .s { color: var(--dim); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#drop .row .v { color: var(--mut); font-size: 12px; font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
#drop .row.on { background: rgb(from var(--acid) r g b / .10); }
#drop .coinrow .caret { color: var(--dim); font-size: 11px; }
#drop hr { border: 0; border-top: 1px solid var(--line); margin: 6px 0; }
#drop .addhdr { padding: 5px 8px 3px; color: var(--dim); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
#drop .netgroup { padding: 2px 4px 6px 40px; }
#drop .netgroup[hidden] { display: none; }
#drop .netchoice { padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 12.5px; color: var(--mut); }
#drop .netchoice:hover { background: rgba(255,255,255,.06); color: var(--ink); }
.ttag { display: inline-block; font-size: 9.5px; letter-spacing: .06em; padding: 1px 5px; border-radius: 3px;
background: rgba(224,179,65,.18); color: #e0b341; font-weight: 700; vertical-align: middle; margin-left: 2px; }
#hNet { color: var(--dim); font-size: 11px; margin-left: 4px; font-weight: 500; }
.picker-actions { display: flex; align-items: center; gap: 6px; }
.chip { background: transparent; border: 1px solid var(--line); color: var(--mut); border-radius: 6px;
padding: 0 8px; height: 22px; cursor: pointer; font: inherit; font-size: 13.5px; line-height: 1;
display: inline-flex; align-items: center; justify-content: center; }
.chip:hover { border-color: var(--acid); color: var(--acid); }
nav { display: flex; border-bottom: 1px solid var(--line); background: var(--panel); }
nav button { flex: 1; padding: 9px 0 8px; border: 0; background: transparent; color: var(--mut); cursor: pointer;
font: inherit; font-size: 12.5px; border-bottom: 2px solid transparent; }
nav button.on { color: var(--acid); border-bottom-color: var(--acid); }
nav button:hover { color: var(--ink); }
main { flex: 1; overflow: auto; padding: 14px; }
section[hidden] { display: none; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 12px; }
.mono { font: 12.5px/1.45 ui-monospace, "Cascadia Code", Consolas, monospace; word-break: break-all; }
.lbl { color: var(--dim); font-size: 11.5px; margin-bottom: 4px; }
.btn { padding: 7px 12px; border-radius: 7px; border: 1px solid var(--line); background: var(--panel); color: var(--ink);
cursor: pointer; font: inherit; font-size: 12.5px; }
.btn:hover { border-color: rgb(from var(--acid) r g b / .4); }
.btn.primary { background: var(--acid); color: #0b0e14; border-color: transparent; font-weight: 650; }
.btn.primary:disabled { opacity: .45; cursor: default; }
.btn.danger { color: var(--danger); }
.btn.sm { padding: 4px 9px; font-size: 12px; }
.actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.qrwrap { display: grid; place-items: center; padding: 12px; background: #fff; border-radius: 10px; margin-bottom: 12px; }
canvas { image-rendering: pixelated; }
input[type=text], input[type=number], textarea { width: 100%; padding: 7px 9px; border-radius: 7px; background: var(--panel);
border: 1px solid var(--line); color: var(--ink); font: inherit; font-size: 13px; outline: none; }
input:focus, textarea:focus { border-color: rgb(from var(--acid) r g b / .5); }
textarea { resize: vertical; min-height: 96px; font: 12px/1.45 ui-monospace, Consolas, monospace; }
.field { margin-bottom: 12px; }
.hint { color: var(--dim); font-size: 11.5px; margin-top: 4px; }
.amt { display: flex; gap: 6px; }
.amt input { flex: 1; }
.unit { display: flex; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
.unit button { border: 0; background: var(--panel); color: var(--mut); padding: 0 10px; cursor: pointer; font: inherit; font-size: 12px; }
.unit button.on { background: rgb(from var(--acid) r g b / .14); color: var(--acid); }
.fee { display: flex; align-items: center; gap: 10px; }
.fee input[type=range] { flex: 1; accent-color: var(--acid); }
.fee .v { color: var(--mut); font-size: 12px; min-width: 70px; text-align: right; font-variant-numeric: tabular-nums; }
.summary { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: 12.5px; margin-top: 10px; }
.summary .k { color: var(--dim); } .summary .v { text-align: right; font-variant-numeric: tabular-nums; }
.msg { margin-top: 10px; font-size: 12.5px; padding: 8px 10px; border-radius: 7px; }
.msg.err { background: rgba(246,118,138,.12); color: var(--danger); }
.msg.ok { background: rgba(90,211,138,.12); color: var(--ok); }
.msg a { color: inherit; }
.tx { display: grid; grid-template-columns: 22px 1fr auto; gap: 2px 10px; padding: 9px 4px; border-bottom: 1px solid var(--line); cursor: pointer; }
.tx:hover { background: rgba(255,255,255,.03); }
.tx .ic { font-size: 15px; line-height: 1.3; }
.tx .ic.in { color: var(--ok); } .tx .ic.out { color: var(--danger); }
.tx .what { font-size: 12.5px; }
.tx .when { color: var(--dim); font-size: 11.5px; grid-column: 2; }
.tx .amt2 { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; font-size: 12.5px; }
.tx .amt2.in { color: var(--ok); }
.tx .conf { grid-column: 3; text-align: right; color: var(--dim); font-size: 11px; }
.tx .conf.pending { color: #e0b341; }
.empty { color: var(--dim); text-align: center; padding: 30px 10px; font-size: 12.5px; }
.gate { padding: 40px 18px; text-align: center; color: var(--mut); }
.gate .big { font-size: 28px; margin-bottom: 8px; }
.gate b { color: var(--ink); }
.kv { margin-top: 10px; }
.kv .lbl { margin-top: 8px; }
a.link { color: var(--acid); text-decoration: none; cursor: pointer; }
</style>
</head>
<body>
<header>
<div class="picker" id="pickerBtn">
<div class="t">
<span class="badge" id="hBadge"></span>
<span class="lbl" id="hLabel">Aegis Wallet</span>
<span id="hNet"></span>
</div>
<div class="picker-actions">
<button type="button" id="hAddWallet" class="chip" title="Add a new wallet">+</button>
<span class="caret">▾</span>
</div>
</div>
<div id="drop" hidden></div>
<div class="bal">
<div class="big"><span id="balMain">—</span><small id="balTicker">—</small></div>
<div class="sub">
<span class="netlbl" id="netlbl">connecting…</span>
<span class="net"><span class="dot" id="dot"></span></span>
</div>
</div>
</header>
<nav>
<button data-tab="receive" class="on">Receive</button>
<button data-tab="send">Send</button>
<button data-tab="history">History</button>
<button data-tab="settings">Settings</button>
</nav>
<main>
<div id="gate" class="gate" hidden></div>
<div id="tabs">
<section id="tab-receive">
<div class="qrwrap"><canvas id="qr" width="200" height="200"></canvas></div>
<div class="card">
<div class="lbl">Receiving address <span id="addrMeta"></span></div>
<div class="mono" id="addr">—</div>
<div class="actions">
<button class="btn sm" id="copyAddr">Copy</button>
<button class="btn sm" id="nextAddr" hidden>Next unused address</button>
<button class="btn sm" id="viewAddr">Explorer</button>
<button class="btn sm" id="openFaucet" hidden>Faucet</button>
</div>
</div>
<div class="card" id="tokensCard" hidden style="margin-top:12px">
<div class="lbl">Tokens</div>
<div id="tokensList" class="kv"></div>
<div class="hint">SPL tokens held by this wallet. Send by picking one under the Send tab's Asset dropdown.</div>
</div>
</section>
<section id="tab-send" hidden>
<div class="field" id="sendAssetField" hidden>
<div class="lbl">Asset</div>
<select id="sendAsset"></select>
<div class="hint">Pick the native coin or an SPL token in this wallet.</div>
</div>
<div class="field">
<div class="lbl">Recipient</div>
<input type="text" id="sendTo" spellcheck="false" autocomplete="off" placeholder="…">
<div class="hint" id="sendToHint"></div>
</div>
<div class="field">
<div class="lbl">Amount</div>
<div class="amt">
<input type="text" id="sendAmt" inputmode="decimal" autocomplete="off" placeholder="0.00">
<div class="unit" id="unitPicker"></div>
<button class="btn sm" id="sendMax" type="button">Max</button>
</div>
</div>
<div class="field" id="feeField">
<div class="lbl">Fee</div>
<div class="fee"><input type="range" id="feeRate" min="1" max="5" step="1" value="1"><span class="v" id="feeLbl">1 sat/B</span></div>
</div>
<div class="card">
<div class="summary">
<div class="k">Amount</div><div class="v" id="sumAmt">—</div>
<div class="k">Network fee</div><div class="v" id="sumFee">—</div>
<div class="k">Total</div><div class="v" id="sumTotal">—</div>
</div>
</div>
<div class="actions"><button class="btn primary" id="sendBtn" disabled>Send</button></div>
<div class="msg" id="sendMsg" hidden></div>
</section>
<section id="tab-history" hidden>
<div id="txlist"></div>
</section>
<section id="tab-settings" hidden>
<div class="card" id="walletManage" style="margin-bottom:14px">
<div class="lbl">This wallet</div>
<div class="field" style="margin-top:6px;margin-bottom:8px">
<input type="text" id="renameLabel" placeholder="Wallet name">
</div>
<div class="actions">
<button class="btn sm" id="renameBtn">Rename</button>
<button class="btn sm danger" id="removeBtn">Remove wallet</button>
</div>
<div class="hint" id="removeHint"></div>
</div>
<div id="bchSettings" hidden>
<div class="field">
<div class="lbl">Derivation path (account)</div>
<input type="text" id="setPath" spellcheck="false" placeholder="m/44'/145'/0'">
<div class="hint">Changing this switches to a different set of addresses under the same wallet seed.</div>
</div>
<div class="field" id="bchServersRow">
<div class="lbl">Electrum servers (shared across BCH mainnet wallets, one per line)</div>
<textarea id="setServers" spellcheck="false"></textarea>
<div class="hint" id="serverHint"></div>
</div>
<div class="actions">
<button class="btn primary" id="applySettings">Apply</button>
<button class="btn" id="resetServers">Reset servers</button>
</div>
<div class="card" style="margin-top:16px">
<div class="lbl">Recovery info</div>
<div class="hint">Keys come from your Theseus password vault under <span class="mono" id="purpose"></span>. The same vault seed on another machine recreates this wallet.</div>
<div class="kv" id="recovery"></div>
<div class="actions">
<button class="btn" id="showXpub">Show account xpub</button>
<button class="btn danger" id="showXprv">Show account private key</button>
</div>
</div>
</div>
<div id="trxSettings" hidden>
<div class="card">
<div class="lbl">Recovery info</div>
<div class="hint">Keys come from your Theseus password vault under <span class="mono" id="trxPurpose"></span>. Import into TronLink with the derivation path <span class="mono">m/44'/195'/0'/0/0</span> from the same vault seed.</div>
</div>
</div>
<div id="scSettings" hidden>
<div class="field">
<div class="lbl">walletd URL</div>
<input type="text" id="setWalletdUrl" spellcheck="false" placeholder="https://your-walletd.example/api">
<div class="hint">Any public or self-hosted <span class="mono">go.sia.tech/walletd</span> in "full" index mode. The URL is per-wallet, so different Sia sub-accounts can point at different nodes. It is not shared with anyone else.</div>
</div>
<div class="actions">
<button class="btn primary" id="applyWalletdUrl">Apply</button>
</div>
<div class="card" style="margin-top:16px">
<div class="lbl">Recovery info</div>
<div class="hint">Keys come from your Theseus password vault under <span class="mono" id="scPurpose"></span>. Address scheme: walletd <span class="mono">KeyFromSeed(seed, index)</span>.</div>
<div class="kv" id="scRecovery"></div>
<div class="actions">
<button class="btn danger" id="showScSeed">Reveal wallet seed</button>
</div>
</div>
</div>
<div id="dgbSettings" hidden>
<div class="field">
<div class="lbl">Address family</div>
<select id="setDgbFamily"></select>
<div class="hint">Picks the BIP purpose that shapes your DGB addresses. Switching rebuilds the wallet against a different set of addresses under the same seed — old funds don't move; they still live under the family they were received on.</div>
</div>
<div class="field">
<div class="lbl">Derivation path (account)</div>
<input type="text" id="setDgbPath" spellcheck="false" placeholder="m/84'/20'/0'">
<div class="hint">Auto-filled from the family above. Edit only if you need a non-default account.</div>
</div>
<div class="actions">
<button class="btn primary" id="applyDgbPath">Apply</button>
</div>
<div class="card" style="margin-top:16px">
<div class="lbl">Recovery info</div>
<div class="hint">Keys come from your Theseus password vault under <span class="mono" id="dgbPurpose"></span>. Any BIP39 tool set to <span class="mono">DGB</span> (coin type 20) at the same purpose can reproduce this wallet.</div>
<div class="kv" id="dgbRecovery"></div>
<div class="actions">
<button class="btn" id="showDgbXpub">Show account xpub</button>
<button class="btn danger" id="showDgbXprv">Show account private key</button>
</div>
</div>
</div>
<div id="btcSettings" hidden>
<div class="field">
<div class="lbl">Address family</div>
<select id="setBtcFamily"></select>
<div class="hint">Picks the BIP purpose that shapes your Bitcoin addresses. Switching rebuilds the wallet against a different set of addresses under the same seed — old funds don't move; they still live under the family they were received on.</div>
</div>
<div class="field">
<div class="lbl">Derivation path (account)</div>
<input type="text" id="setBtcPath" spellcheck="false" placeholder="m/84'/0'/0'">
<div class="hint">Auto-filled from the family above. Edit only if you need a non-default account.</div>
</div>
<div class="actions">
<button class="btn primary" id="applyBtcPath">Apply</button>
</div>
<div class="card" style="margin-top:16px">
<div class="lbl">Recovery info</div>
<div class="hint">Keys come from your Theseus password vault under <span class="mono" id="btcPurpose"></span>. Any BIP39 tool at coin type 0 (mainnet) / 1 (testnet) and the same purpose can reproduce this wallet.</div>
<div class="kv" id="btcRecovery"></div>
<div class="actions">
<button class="btn" id="showBtcXpub">Show account xpub</button>
<button class="btn danger" id="showBtcXprv">Show account private key</button>
</div>
</div>
</div>
<div id="ethSettings" hidden>
<div class="field">
<div class="lbl">RPC URL</div>
<input type="text" id="setEthRpcUrl" spellcheck="false" placeholder="https://cloudflare-eth.com">
<div class="hint">JSON-RPC endpoint Aegis reads balances and broadcasts from. Any provider works — public gateways (Cloudflare, PublicNode), Alchemy, Infura, or your own node. Stored per-wallet; not shared.</div>
</div>
<div class="actions">
<button class="btn primary" id="applyEthRpc">Apply</button>
</div>
<div class="card" style="margin-top:16px">
<div class="lbl">Recovery info</div>
<div class="hint">Keys come from your Theseus password vault under <span class="mono" id="ethPurpose"></span>. Import into MetaMask / any Ethereum wallet using the derivation path <span class="mono">m/44'/60'/0'/0/0</span> from the same vault seed.</div>
<div class="kv" id="ethRecovery"></div>
<div class="actions">
<button class="btn danger" id="showEthKey">Reveal private key</button>
</div>
</div>
</div>
<div id="solSettings" hidden>
<div class="field">
<div class="lbl">RPC URL</div>
<input type="text" id="setSolRpcUrl" spellcheck="false" placeholder="https://api.mainnet-beta.solana.com">
<div class="hint">JSON-RPC endpoint. The public Solana RPCs are heavily rate-limited — for real use point Aegis at Helius / QuickNode / your own node.</div>
</div>
<div class="actions">
<button class="btn primary" id="applySolRpc">Apply</button>
</div>
<div class="card" style="margin-top:16px">
<div class="lbl">Recovery info</div>
<div class="hint">Keys come from your Theseus password vault under <span class="mono" id="solPurpose"></span>. Import into Phantom / Solflare with the derivation path <span class="mono">m/44'/501'/0'/0'</span> from the same vault seed.</div>
<div class="kv" id="solRecovery"></div>
<div class="actions">
<button class="btn danger" id="showSolKey">Reveal wallet seed</button>
</div>
</div>
</div>
<div class="card" style="margin-top:16px">
<div class="lbl">Connected sites</div>
<div class="hint">Sites allowed to see your address, allowances for silent BCH payments, and Tron dapps you've connected. Message signing always asks.</div>
<div id="sites" class="kv"></div>
</div>
<div class="msg err" id="settingsMsg" hidden></div>
</section>
</div>
</main>
<script src="qr.js"></script>
<script src="panel.js"></script>
</body>
</html>