diff --git a/bundled-addons/aegis/addon.json b/bundled-addons/aegis/addon.json
index f50300d8..0153d390 100644
--- a/bundled-addons/aegis/addon.json
+++ b/bundled-addons/aegis/addon.json
@@ -1,7 +1,7 @@
{
"id": "aegis",
"name": "Aegis Wallet",
- "version": "0.18.0",
+ "version": "0.19.0",
"category": "plugin",
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
"author": "Silent Mode",
diff --git a/bundled-addons/aegis/panel.html b/bundled-addons/aegis/panel.html
index 6b70b579..c454abf6 100644
--- a/bundled-addons/aegis/panel.html
+++ b/bundled-addons/aegis/panel.html
@@ -780,9 +780,23 @@
Chips give one shape for every coin and a visible answer either
way. (0.9.2 had already reordered the stack for the same reason —
this replaces that workaround.) -->
+
diff --git a/bundled-addons/aegis/panel.js b/bundled-addons/aegis/panel.js
index f331a1ea..0a7fe177 100644
--- a/bundled-addons/aegis/panel.js
+++ b/bundled-addons/aegis/panel.js
@@ -2140,15 +2140,20 @@ function paintSendMode() {
// across re-renders and across wallet switches, because someone comparing
// token balances between wallets should not be knocked back to the QR on
// every click.
+const RCV_VIEWS = ["address", "assets", "nfts"];
let rcvView = "address";
-try { rcvView = localStorage.getItem("aegis/rcvView") === "assets" ? "assets" : "address"; } catch (_e) {}
+try {
+ const saved = localStorage.getItem("aegis/rcvView");
+ if (RCV_VIEWS.includes(saved)) rcvView = saved;
+} catch (_e) {}
function paintRcvView() {
- const addr = $("rcvAddressPane"), assets = $("rcvAssetsPane");
- if (!addr || !assets) return;
+ const addr = $("rcvAddressPane"), assets = $("rcvAssetsPane"), nfts = $("rcvNftsPane");
+ if (!addr || !assets || !nfts) return;
document.querySelectorAll("[data-rcv-view]").forEach((b) =>
b.classList.toggle("on", b.dataset.rcvView === rcvView));
addr.hidden = rcvView !== "address";
assets.hidden = rcvView !== "assets";
+ nfts.hidden = rcvView !== "nfts";
// The QR's backing store is sized from its rendered box, so one drawn
// while its pane was hidden comes out blank. Redraw on reveal — and note
// renderReceive only redraws when the address CHANGES, so switching back
@@ -3397,6 +3402,63 @@ function setAssetsCount(n) {
if (cnt) cnt.textContent = "";
}
}
+// Certificates chip. Counts individual certificates, not categories — "3"
+// should mean three things you hold, which is the question the chip is
+// answering. A chain with no non-fungible concept lands here with 0 and says
+// so, rather than the chip vanishing on some coins and not others.
+function setCertsCount(n) {
+ const chip = $("rcvNftsCount");
+ if (chip) chip.textContent = n ? String(n) : "";
+ const card = $("nftsCard");
+ if (card) card.hidden = false;
+ if (!n) {
+ const el = $("nftsList");
+ if (el) el.innerHTML = `
No certificates held by this wallet.
`;
+ const cnt = $("nftsCount");
+ if (cnt) cnt.textContent = "";
+ }
+}
+
+// One row per certificate: which category it belongs to, its commitment (the
+// payload that makes it specific), and its capability — minting / mutable /
+// none is the difference between a certificate that can still issue others
+// and one that is fixed, which is worth seeing at a glance.
+function renderCerts(balances, metaMap) {
+ const rows = [];
+ for (const cat of Object.keys(balances || {})) {
+ const b = balances[cat];
+ const list = Array.isArray(b?.nfts) ? b.nfts : [];
+ for (const n of list) rows.push({ cat, ...n });
+ }
+ setCertsCount(rows.length);
+ if (!rows.length) return;
+ const el = $("nftsList");
+ if (!el) return;
+ const cntEl = $("nftsCount");
+ if (cntEl) cntEl.textContent = `· ${rows.length}`;
+ el.innerHTML = rows.map((r) => {
+ const meta = metaMap?.[r.cat] || null;
+ const named = meta?.name || meta?.symbol || null;
+ const title = named || (r.cat.slice(0, 10) + "…" + r.cat.slice(-6));
+ const cap = String(r.capabilityLabel || r.capability || "none");
+ const capTag = cap && cap !== "none"
+ ? `
${esc(cap.toUpperCase())}`
+ : "";
+ // A commitment is arbitrary bytes; it is the only thing distinguishing
+ // two certificates of the same category, so show it rather than hide it.
+ const commit = String(r.commitmentHex || "");
+ const commitTxt = commit
+ ? (commit.length > 24 ? commit.slice(0, 24) + "…" : commit)
+ : "no commitment";
+ return `
+
+
${esc(title)}${capTag}
+
${esc(commitTxt)}
+
+
${named ? esc(r.cat.slice(0, 8)) + "…" : ""}
+
`;
+ }).join("");
+}
// Name a token yourself. Self-minted categories publish nothing — no
// registry entry, and usually not even an OP_RETURN in the genesis — so
@@ -3467,6 +3529,9 @@ function renderTokens() {
const s = sel();
const card = $("tokensCard");
const el = $("tokensList");
+ // Certificates are BCH-only today; every other chain lands on 0 and says
+ // so. Overwritten below for BCH.
+ if (chain() !== "bch") setCertsCount(0);
// SOL wallets: SPL tokens with a Send button (existing flow).
if (chain() === "sol") {
const tokens = s?.tokens || [];
@@ -3495,27 +3560,33 @@ function renderTokens() {
// come from BCMR, fetched async; the first paint uses raw category hex.
if (chain() === "bch") {
const balances = s?.tokenBalances || {};
- const cats = Object.keys(balances);
+ const allCats = Object.keys(balances);
+ // Assets = categories carrying a fungible balance. Certificates live in
+ // their own pane, so a category that is purely non-fungible does not
+ // take up a row here saying "1 NFT".
+ const cats = allCats.filter((c) => String(balances[c]?.fungible || "0") !== "0");
setAssetsCount(cats.length);
const hintEl = $("tokensHint");
- if (hintEl) hintEl.textContent = "CashTokens held by this wallet. Names come from BCMR — configure custom registries in Settings.";
- if (!cats.length) return;
+ if (hintEl) hintEl.textContent = "Fungible CashTokens held by this wallet. Names come from BCMR — configure custom registries in Settings.";
const draw = (metaMap) => {
+ // setAssetsCount already wrote the "no assets" line when there are no
+ // fungible categories; joining an empty list would wipe it. Common
+ // case now that purely-non-fungible categories live in the other pane.
+ if (!cats.length) { renderCerts(balances, metaMap); return; }
el.innerHTML = cats.map((cat) => {
const b = balances[cat];
const meta = metaMap?.[cat] || null;
const decimals = meta && Number.isFinite(meta.decimals) ? meta.decimals : 0;
const fungibleRaw = String(b.fungible || "0");
const showFungible = fungibleRaw !== "0";
- const nftCount = Array.isArray(b.nfts) ? b.nfts.length : 0;
+
const name = meta?.name || meta?.symbol || null;
const symbol = meta?.symbol || "";
const icon = meta?.iconUri || "";
const iconHtml = icon ? `
})
` : "";
const catShort = cat.slice(0, 10) + "…" + cat.slice(-6);
const fungibleTxt = showFungible ? fmtTokenAmount(fungibleRaw, decimals) + (symbol ? " " + symbol : "") : "";
- const nftTxt = nftCount ? `${nftCount} NFT${nftCount === 1 ? "" : "s"}` : "";
- const amountLine = [fungibleTxt, nftTxt].filter(Boolean).join(" · ") || "—";
+ const amountLine = fungibleTxt || "—";
const localTag = meta?.local ? `
YOURS` : "";
// Named: name on top, category beneath. Unnamed: the category IS the
// only identity, so it goes on top and the sub-line says what it is
@@ -3539,12 +3610,14 @@ function renderTokens() {
el.querySelectorAll("[data-tokname]").forEach((b) => b.addEventListener("click", () => {
openTokenLabelModal(b.dataset.tokname, metaMap?.[b.dataset.tokname] || null);
}));
+ renderCerts(balances, metaMap);
};
// Paint immediately with whatever we know synchronously so the row
// set doesn't wait for the network. Then run the async lookup and
// redraw with names + icons.
draw({});
- S.invoke("tokenMetadata", { categories: cats }).then((res) => {
+ if (!allCats.length) return;
+ S.invoke("tokenMetadata", { categories: allCats }).then((res) => {
if (chain() !== "bch") return; // user switched away mid-fetch
// 0.17.0: the reply carries the map under .meta plus whether any
// registry actually answered. The old shape was the bare map and the
@@ -3983,7 +4056,7 @@ if ($("rcvConsolidateBack")) $("rcvConsolidateBack").addEventListener("click", (
});
// Address & QR / Assets — the persistent pair inside the Receive body.
document.querySelectorAll("[data-rcv-view]").forEach((b) => b.addEventListener("click", () => {
- rcvView = b.dataset.rcvView === "assets" ? "assets" : "address";
+ rcvView = RCV_VIEWS.includes(b.dataset.rcvView) ? b.dataset.rcvView : "address";
try { localStorage.setItem("aegis/rcvView", rcvView); } catch (_e) {}
paintRcvView();
}));