diff --git a/bundled-addons/aegis/addon.json b/bundled-addons/aegis/addon.json
index 63f66378..7ff71e12 100644
--- a/bundled-addons/aegis/addon.json
+++ b/bundled-addons/aegis/addon.json
@@ -1,7 +1,7 @@
{
"id": "aegis",
"name": "Aegis Wallet",
- "version": "0.9.1",
+ "version": "0.9.2",
"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/lib/chain-generic-imported.js b/bundled-addons/aegis/lib/chain-generic-imported.js
index c170c433..9b4ff497 100644
--- a/bundled-addons/aegis/lib/chain-generic-imported.js
+++ b/bundled-addons/aegis/lib/chain-generic-imported.js
@@ -150,7 +150,45 @@ module.exports = function makeGenericImportedAdapter() {
const j = await r.json();
return String(j?.balance || 0);
},
- async fetchHistory({ rpc, address }) {
+ // Native TRX transfers AND TRC20 token transfers, merged newest-first
+ // and each tagged with `asset` so the History tab can filter. Token
+ // movement is invisible in the native feed — a wallet that only ever
+ // moved USDT looked like it had no history at all.
+ async fetchHistory(opts) {
+ const [native, tokens] = await Promise.all([
+ CHAIN_CFGS.trx._fetchNativeHistory(opts),
+ CHAIN_CFGS.trx._fetchTokenHistory(opts).catch(() => []),
+ ]);
+ return [...native, ...tokens]
+ .sort((a, b) => (b.time || 0) - (a.time || 0))
+ .slice(0, 40);
+ },
+ async _fetchTokenHistory({ rpc, address }) {
+ const r = await fetch(`${rpc.replace(/\/+$/, "")}/v1/accounts/${encodeURIComponent(address)}/transactions/trc20?limit=25`);
+ if (!r.ok) throw new Error(`Tron trc20 history HTTP ${r.status}`);
+ const j = await r.json();
+ const list = Array.isArray(j?.data) ? j.data : [];
+ const me = String(address);
+ return list.map((t) => {
+ const ti = t.token_info || {};
+ const outgoing = String(t.from || "") === me;
+ const raw = String(t.value || "0");
+ return {
+ txid: t.transaction_id,
+ time: Math.floor(Number(t.block_timestamp || 0) / 1000),
+ confirmations: 1,
+ status: "confirmed",
+ // Token amounts are in the TOKEN's own decimals, not the
+ // chain's, so they travel with their own scale rather than
+ // being rendered against the native one.
+ delta: (outgoing ? "-" : "") + raw,
+ assetDecimals: Number.isFinite(Number(ti.decimals)) ? Number(ti.decimals) : 0,
+ asset: cleanTokenText(ti.symbol) || "token",
+ kind: "TRC20",
+ };
+ }).filter((t) => t.txid);
+ },
+ async _fetchNativeHistory({ rpc, address }) {
const r = await fetch(`${rpc.replace(/\/+$/, "")}/v1/accounts/${encodeURIComponent(address)}/transactions?limit=25`);
if (!r.ok) throw new Error(`Tron history HTTP ${r.status}`);
const j = await r.json();
@@ -171,6 +209,7 @@ module.exports = function makeGenericImportedAdapter() {
status: ok ? "confirmed" : "failed",
// Aegis renders `delta` in the wallet's base unit (sun here).
delta: c?.type === "TransferContract" ? (outgoing ? -amount : amount) : 0,
+ asset: null, // null = the chain's native coin
kind: c?.type || "Contract",
};
}).filter((t) => t.txid);
diff --git a/bundled-addons/aegis/panel.html b/bundled-addons/aegis/panel.html
index da094776..44291c13 100644
--- a/bundled-addons/aegis/panel.html
+++ b/bundled-addons/aegis/panel.html
@@ -35,8 +35,8 @@
.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; }
+ .bal .sub { color: var(--dim); font-size: 11.5px; display: flex; align-items: center; gap: 8px; }
+ .bal .sub .netlbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Full-panel sheet — fills most of the sidebar but stops above the
footer so the aegis.x brand + version chip stay visible. Users
opening + should still know which build they're on and be able
@@ -79,6 +79,16 @@
#drop .brnet.on { background: rgb(from var(--acid, #d6ff3d) r g b / .14); border-color: rgb(from var(--acid, #d6ff3d) r g b / .55);
color: var(--acid, #d6ff3d); font-weight: 600; }
#drop .brnet .hint { color: currentColor; opacity: .65; font-size: 11px; }
+ .histfilter { display: flex; align-items: center; gap: 14px; padding: 0 2px 10px;
+ border-bottom: 1px solid var(--line); margin-bottom: 8px; font-size: 12px; }
+ .histfilter[hidden] { display: none; }
+ /* Assets card rows (0.9.2). The native coin leads the list; a symbol
+ claimed by several contracts gets a LOOK-ALIKE tag so a borrowed
+ ticker can't pass for the real token. */
+ .tx.asset { padding: 7px 8px; border-radius: 6px; }
+ .tx.asset:hover { background: rgba(255,255,255,.04); }
+ .tx.asset.native { background: rgb(from var(--acid, #d6ff3d) r g b / .06); }
+ .ttag.tdupe { background: rgba(224,90,90,.18); color: #f6768a; cursor: help; }
.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; }
/* 0.8.4: hNet became a proper chip on its own row (.netrow) so the
@@ -99,6 +109,13 @@
#drop .catgroup { padding: 4px 6px 2px 10px; color: var(--dim); font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; }
#drop .row.unowned { opacity: .8; }
#drop .row.unowned .v { color: var(--acid, #d6ff3d); font-weight: 600; font-size: 11px; }
+ /* Per-wallet actions promoted into the header's status row (0.9.2). */
+ .hdracts { margin-left: auto; display: inline-flex; gap: 4px; }
+ .hdract { background: transparent; border: 1px solid var(--line); color: var(--dim);
+ border-radius: 999px; padding: 1px 9px; font: inherit; font-size: 10.5px;
+ cursor: pointer; line-height: 1.5; }
+ .hdract:hover { border-color: var(--acid, #d6ff3d); color: var(--acid, #d6ff3d); }
+ .hdract[hidden] { display: none; }
.fiat { color: var(--dim); font-size: 12.5px; margin-left: 10px; font-weight: 500; letter-spacing: .2px; }
.portfolio { margin-top: 6px; color: var(--mut); font-size: 11.5px; }
.portfolio b { color: var(--ink); font-weight: 600; }
@@ -198,7 +215,8 @@
.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; }
+ .qrwrap { display: grid; place-items: center; padding: 12px; background: #fff; border-radius: 10px; margin-top: 12px; }
+ .qrwrap[hidden] { display: none; }
canvas { image-rendering: pixelated; }
input[type=text], input[type=number], input[type=password], 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; }
@@ -567,6 +585,14 @@
connecting…
+
+
+
+
+
@@ -595,19 +621,22 @@
-
+
Receiving address
—
+
-
-
+
-
Tokens
+
Assets
Tokens held by this wallet.
@@ -679,6 +708,14 @@
+
+
+
+
+
+
diff --git a/bundled-addons/aegis/panel.js b/bundled-addons/aegis/panel.js
index cdb5f425..1853e047 100644
--- a/bundled-addons/aegis/panel.js
+++ b/bundled-addons/aegis/panel.js
@@ -2463,46 +2463,8 @@ function wireStripDragDrop(el, chainGroups) {
// Inline replacement for the modal address list. Rendered directly into
// the wallet strip element when stripView.mode === "addresses". Header
// row has a back arrow (returns to the coins summary) and the coin's
-// Which address rows have their asset list expanded, in the coin drilldown.
-// Panel-session only — a drilldown is a transient view, so there's nothing
-// worth persisting across restarts.
-const expandedAddrAssets = new Set();
-
-// Normalise a wallet's assets into one row shape the drilldown can render,
-// regardless of which chain family it came from:
-// account-model (TRC20 / SPL) → w.tokens: [{mint, symbol, name, decimals, balance}]
-// BCH CashTokens → w.tokenBalances: { : {fungible, nfts[]} }
-// Returns [{ id, symbol, name, amount }] with amount already formatted.
-function assetsForWallet(w) {
- const out = [];
- for (const t of (w.tokens || [])) {
- const dec = Number(t.decimals) || 0;
- out.push({
- id: String(t.mint || ""),
- symbol: t.symbol || "?",
- name: t.name || "",
- // Unknown contracts have no decimals — show the raw integer rather
- // than a number invented from an assumed scale.
- amount: t.known === false ? `${t.balance} raw` : fmtTokenAmount(t.balance, dec),
- });
- }
- const tb = w.tokenBalances || {};
- for (const cat of Object.keys(tb)) {
- const b = tb[cat] || {};
- const fungible = String(b.fungible || "0");
- const nftCount = Array.isArray(b.nfts) ? b.nfts.length : 0;
- const parts = [];
- // Decimals live in BCMR, which the drilldown doesn't fetch — show the
- // raw fungible amount and let the Tokens card do the named rendering.
- if (fungible !== "0") parts.push(fungible);
- if (nftCount) parts.push(`${nftCount} NFT${nftCount === 1 ? "" : "s"}`);
- if (!parts.length) continue;
- out.push({ id: cat, symbol: "CashToken", name: "", amount: parts.join(" · ") });
- }
- return out;
-}
-
-// name/logo; body rows show one wallet each with balance + inline ✎ / 🗑.
+// name/logo; body rows show one wallet each with an inline ✎ / 🗑.
+// Assets live in the Receive tab's Assets card, not here — see 0.9.2.
function renderInlineCoinList(el, groupKey, group) {
const { meta, wallets: gw } = group;
const selId = state?.selectedWalletId;
@@ -2514,6 +2476,7 @@ function renderInlineCoinList(el, groupKey, group) {
const totalNative = fmtBig(totalUnits || 0, decimals);
const totalUsd = usdOf(chain, totalUnits || 0, decimals);
const totalFiat = totalUsd != null ? fmtFiat(totalUsd) : "";
+ const multiAddr = gw.length > 1;
const rows = gw.map((w) => {
const on = w.id === selId ? "on" : "";
const units = walletBalanceUnits(w);
@@ -2535,39 +2498,31 @@ function renderInlineCoinList(el, groupKey, group) {
const shortName = shortLabel(w.label || "", 8);
// Fiat is dropped from the row to keep everything on one line; the
// aggregate coin fiat still shows in the drilldown header above.
- // 0.8.8: per-address assets. Each row can expand to show what THIS
- // address holds beyond the native coin — TRC20/SPL via `tokens`, BCH
- // CashTokens via `tokenBalances`. Rows with nothing extra get no
- // chevron so the list stays quiet for plain wallets.
- const assets = assetsForWallet(w);
- const expanded = expandedAddrAssets.has(w.id);
- const assetChip = assets.length
- ? ``
- : "";
+ // 0.9.2: the per-address asset list added in 0.8.8 duplicated the
+ // Receive tab's Assets card, so it's gone — assets live in one place.
+ // The truncated address is gone too: the full one sits at the top of
+ // Receive, and a shortened copy here was a second, less useful
+ // rendering of the same string. The row now carries identity (name)
+ // and, only when there is more than one address to compare, balance.
const canRemoveRow = !(w.isDefault || w.isLegacy);
const rowRemove = canRemoveRow
? ``
: `🔒`;
- const assetRows = expanded && assets.length
- ? `
`
- : "";
+ // With a single address the amount is identical to the header's
+ // balance two rows up; showing it a third time is noise.
+ const amountCell = multiAddr
+ ? `${esc(bal)}${esc(meta.ticker)}`
+ : ``;
return `
${assetRows}`;
+ `;
}).join("");
// Surface any adapter errors from the wallets in this group. If a fetch
// is failing (RPC unreachable, CORS block, rate limit) the display would
@@ -2576,9 +2531,6 @@ function renderInlineCoinList(el, groupKey, group) {
const errs = gw.filter((w) => w.error).map((w) => `${shortLabel(w.label || w.address || "?", 6)}: ${w.error}`);
const errLine = errs.length ? `
`;
const back = () => { stripView = { mode: "coins", groupKey: null }; renderWalletStrip(); };
el.querySelector("#stripBack").addEventListener("click", back);
el.querySelector("#stripBackX").addEventListener("click", back);
@@ -2664,13 +2619,6 @@ function renderInlineCoinList(el, groupKey, group) {
} catch (er) { showErr(cleanErr(er)); }
}));
// Per-address asset list toggle.
- el.querySelectorAll("[data-lpassets]").forEach((b) => b.addEventListener("click", (e) => {
- e.stopPropagation();
- const id = b.dataset.lpassets;
- if (expandedAddrAssets.has(id)) expandedAddrAssets.delete(id);
- else expandedAddrAssets.add(id);
- renderWalletStrip();
- }));
el.querySelector("#stripAddMore").addEventListener("click", async () => {
// Add another wallet of the same coin+network directly, without
// opening the picker sheet — the user is already inside this coin's
@@ -3137,21 +3085,39 @@ function renderTokens() {
card.hidden = tokens.length === 0;
const hintEl = $("tokensHint");
if (hintEl) hintEl.textContent = "TRC20 tokens held by this wallet. Read-only in this build — use the explorer link to move them.";
- if (!tokens.length) return;
- el.innerHTML = tokens.map((t) => {
+ card.hidden = false;
+ // A symbol claimed by more than one contract is the lookalike pattern:
+ // spam mints borrow a trusted ticker so a careless send lands on the
+ // wrong contract. We can't tell which is genuine, so we don't guess —
+ // we mark every member of the clash and let the user check the address.
+ const symCount = new Map();
+ for (const t of tokens) {
+ const k = (t.symbol || "").toLowerCase();
+ if (t.known && k) symCount.set(k, (symCount.get(k) || 0) + 1);
+ }
+ const nativeUnits = walletBalanceUnits(sel()) || 0;
+ const nativeRow = `