Compare commits

..

1 Commits

Author SHA1 Message Date
user
5395fc6ede fix: unify address display with shared renderAddressHtml utility
All checks were successful
check / check (push) Successful in 26s
All address rendering now uses a single renderAddressHtml() function in
helpers.js that produces consistent output everywhere:
- Color dot (deterministic from address)
- Full address with dashed-underline click-to-copy affordance
- Etherscan external link icon

Refactored all callsites across 9 view files:
- approval.js: approvalAddressHtml now delegates to renderAddressHtml,
  added attachCopyHandlers for click-to-copy on approve-tx/sign/site views
- confirmTx.js: confirmAddressHtml uses renderAddressHtml, token contract
  address uses renderAddressHtml with attachCopyHandlers
- txStatus.js: toAddressHtml delegates to renderAddressHtml
- transactionDetail.js: txAddressHtml delegates to renderAddressHtml,
  decoded calldata addresses use renderAddressHtml
- home.js: active address display uses renderAddressHtml
- send.js: from-address display uses renderAddressHtml
- receive.js: address block uses formatAddressHtml (which delegates to
  renderAddressHtml), removed separate etherscan link element
- addressDetail.js: address line uses renderAddressHtml, export-privkey
  address uses renderAddressHtml
- addressToken.js: address line and contract info use renderAddressHtml

Also consolidated:
- EXT_ICON SVG constant moved to helpers.js (removed 6 duplicates)
- copyableHtml() moved to helpers.js (removed duplicate in transactionDetail)
- etherscanLinkHtml() moved to helpers.js (removed duplicates)
- attachCopyHandlers() moved to helpers.js (removed duplicate in txStatus)
- Removed unused local functions (etherscanTokenLink, etherscanAddressLink)
- Cleaned up unused imports across all files

closes #97
2026-03-01 12:41:26 -08:00
16 changed files with 60 additions and 186 deletions

View File

@@ -2,22 +2,10 @@
// Loads state, initializes views, triggers first render.
const { DEBUG } = require("../shared/constants");
const {
state,
saveState,
loadState,
currentNetwork,
} = require("../shared/state");
const { state, saveState, loadState } = require("../shared/state");
const { refreshPrices } = require("../shared/prices");
const { refreshBalances } = require("../shared/balances");
const {
$,
showView,
setRenderMain,
pushCurrentView,
goBack,
clearViewStack,
} = require("./views/helpers");
const { $, showView } = require("./views/helpers");
const { applyTheme } = require("./theme");
const home = require("./views/home");
@@ -65,42 +53,15 @@ async function doRefreshAndRender() {
const ctx = {
renderWalletList,
doRefreshAndRender,
showAddWalletView: () => {
pushCurrentView();
addWallet.show();
},
showAddressDetail: () => {
pushCurrentView();
addressDetail.show();
},
showAddressToken: () => {
pushCurrentView();
addressToken.show();
},
showAddTokenView: () => {
pushCurrentView();
addToken.show();
},
showConfirmTx: (txInfo) => {
pushCurrentView();
confirmTx.show(txInfo);
},
showReceive: () => {
pushCurrentView();
receive.show();
},
showTransactionDetail: (tx) => {
pushCurrentView();
transactionDetail.show(tx);
},
showSettingsView: () => {
pushCurrentView();
settings.show();
},
showSettingsAddTokenView: () => {
pushCurrentView();
settingsAddToken.show();
},
showAddWalletView: () => addWallet.show(),
showAddressDetail: () => addressDetail.show(),
showAddressToken: () => addressToken.show(),
showAddTokenView: () => addToken.show(),
showConfirmTx: (txInfo) => confirmTx.show(txInfo),
showReceive: () => receive.show(),
showTransactionDetail: (tx) => transactionDetail.show(tx),
showSettingsView: () => settings.show(),
showSettingsAddTokenView: () => settingsAddToken.show(),
};
// Views that can be fully re-rendered from persisted state.
@@ -206,25 +167,18 @@ function fallbackView() {
}
async function init() {
await loadState();
applyTheme(state.theme);
const net = currentNetwork();
if (DEBUG || net.isTestnet) {
if (DEBUG) {
const banner = document.createElement("div");
banner.id = "debug-banner";
if (DEBUG && net.isTestnet) {
banner.textContent = "DEBUG / INSECURE [TESTNET]";
} else if (net.isTestnet) {
banner.textContent = "[TESTNET]";
} else {
banner.textContent = "DEBUG / INSECURE";
}
banner.textContent = "DEBUG / INSECURE";
banner.style.cssText =
"background:#c00;color:#fff;text-align:center;font-size:10px;padding:1px 0;font-family:monospace;position:sticky;top:0;z-index:9999;";
document.body.prepend(banner);
}
await loadState();
applyTheme(state.theme);
// Auto-default active address
if (
state.activeAddress === null &&
@@ -254,15 +208,13 @@ async function init() {
.getElementById("view-settings")
.classList.contains("hidden")
) {
goBack();
renderWalletList();
showView("main");
return;
}
pushCurrentView();
settings.show();
});
setRenderMain(renderWalletList);
welcome.init(ctx);
addWallet.init(ctx);
home.init(ctx);

View File

@@ -1,4 +1,4 @@
const { $, showFlash, goBack } = require("./helpers");
const { $, showView, showFlash } = require("./helpers");
const { getTopTokens } = require("../../shared/tokenList");
const { state, saveState } = require("../../shared/state");
const { lookupTokenInfo } = require("../../shared/balances");
@@ -59,12 +59,7 @@ function init(ctx) {
});
await saveState();
ctx.doRefreshAndRender();
// Pop the stack (back to address detail) and re-render it
// so the newly added token is visible immediately.
if (state.viewStack.length > 0) {
state.viewStack.pop();
}
require("./addressDetail").show();
ctx.showAddressDetail();
} catch (e) {
const detail = e.shortMessage || e.message || String(e);
log.errorf("Token lookup failed for", contractAddr, detail);
@@ -74,9 +69,7 @@ function init(ctx) {
}
});
$("btn-add-token-back").addEventListener("click", () => {
goBack();
});
$("btn-add-token-back").addEventListener("click", ctx.showAddressDetail);
}
module.exports = { init, show };

View File

@@ -1,4 +1,4 @@
const { $, showView, showFlash, goBack, clearViewStack } = require("./helpers");
const { $, showView, showFlash } = require("./helpers");
const {
generateMnemonic,
hdWalletFromMnemonic,
@@ -143,7 +143,6 @@ async function importMnemonic(ctx) {
state.wallets.push(wallet);
state.hasWallet = true;
await saveState();
clearViewStack();
ctx.renderWalletList();
showView("main");
@@ -199,7 +198,6 @@ async function importPrivateKey(ctx) {
});
state.hasWallet = true;
await saveState();
clearViewStack();
ctx.renderWalletList();
showView("main");
@@ -251,7 +249,6 @@ async function importXprvKey(ctx) {
state.wallets.push(wallet);
state.hasWallet = true;
await saveState();
clearViewStack();
ctx.renderWalletList();
showView("main");
@@ -300,7 +297,12 @@ function init(ctx) {
// Back button
$("btn-add-wallet-back").addEventListener("click", () => {
goBack();
if (!state.hasWallet) {
showView("welcome");
} else {
ctx.renderWalletList();
showView("main");
}
});
}

View File

@@ -10,8 +10,6 @@ const {
truncateMiddle,
renderAddressHtml,
attachCopyHandlers,
goBack,
pushCurrentView,
} = require("./helpers");
const { state, currentAddress, saveState } = require("../../shared/state");
const { formatUsd, getAddressValueUsd } = require("../../shared/prices");
@@ -249,7 +247,8 @@ function init(_ctx) {
ctx = _ctx;
$("btn-address-back").addEventListener("click", () => {
goBack();
ctx.renderWalletList();
showView("main");
});
$("btn-send").addEventListener("click", () => {
@@ -267,7 +266,6 @@ function init(_ctx) {
$("send-token-static").classList.add("hidden");
updateSendBalance();
resetSendValidation();
pushCurrentView();
showView("send");
});
@@ -297,7 +295,6 @@ function init(_ctx) {
$("btn-export-privkey").addEventListener("click", () => {
moreDropdown.classList.add("hidden");
moreBtn.classList.remove("bg-fg", "text-bg");
pushCurrentView();
const wallet = state.wallets[state.selectedWallet];
const addr = wallet.addresses[state.selectedAddress];
const blockieEl = $("export-privkey-jazzicon");
@@ -370,7 +367,7 @@ function init(_ctx) {
$("btn-export-privkey-back").addEventListener("click", () => {
$("export-privkey-value").textContent = "";
$("export-privkey-password").value = "";
goBack();
show();
});
}

View File

@@ -13,8 +13,6 @@ const {
balanceLine,
renderAddressHtml,
attachCopyHandlers,
goBack,
pushCurrentView,
} = require("./helpers");
const { state, currentAddress, saveState } = require("../../shared/state");
const { TOKEN_BY_ADDRESS, resolveSymbol } = require("../../shared/tokenList");
@@ -333,7 +331,7 @@ function init(_ctx) {
});
$("btn-address-token-back").addEventListener("click", () => {
goBack();
ctx.showAddressDetail();
});
$("btn-address-token-send").addEventListener("click", () => {
@@ -367,7 +365,6 @@ function init(_ctx) {
attachCopyHandlers($("send-token-static"));
updateSendBalance();
resetSendValidation();
pushCurrentView();
showView("send");
});

View File

@@ -20,7 +20,6 @@ const {
escapeHtml,
renderAddressHtml,
attachCopyHandlers,
goBack,
} = require("./helpers");
const { state, currentNetwork } = require("../../shared/state");
const { getSignerForAddress } = require("../../shared/wallet");
@@ -356,7 +355,7 @@ function init(ctx) {
});
$("btn-confirm-back").addEventListener("click", () => {
goBack();
showView("send");
});
}

View File

@@ -1,4 +1,4 @@
const { $, showView, showFlash, goBack, clearViewStack } = require("./helpers");
const { $, showView, showFlash } = require("./helpers");
const { state, saveState } = require("../../shared/state");
const { decryptWithPassword } = require("../../shared/vault");
@@ -21,7 +21,7 @@ function init(_ctx) {
$("btn-delete-wallet-back").addEventListener("click", () => {
deleteWalletIndex = null;
goBack();
ctx.showSettingsView();
});
$("btn-delete-wallet-confirm").addEventListener("click", async () => {
@@ -77,7 +77,6 @@ function init(_ctx) {
state.selectedWallet = null;
state.selectedAddress = null;
state.activeAddress = null;
clearViewStack();
await saveState();
showView("welcome");
} else {
@@ -87,14 +86,8 @@ function init(_ctx) {
state.activeAddress =
state.wallets[0].addresses[0]?.address || null;
await saveState();
// Reset stack to [main] so Settings back goes home.
// Use require() lazily to avoid circular dependency
// (settings.js requires deleteWallet.js).
clearViewStack();
state.viewStack.push("main");
ctx.renderWalletList();
const settings = require("./settings");
settings.show();
ctx.showSettingsView();
showFlash("Wallet deleted.");
}
});

View File

@@ -59,59 +59,14 @@ function showView(name) {
clearFlash();
state.currentView = name;
saveState();
const net = currentNetwork();
if (DEBUG || net.isTestnet) {
if (DEBUG) {
const banner = document.getElementById("debug-banner");
if (banner) {
if (DEBUG && net.isTestnet) {
banner.textContent =
"DEBUG / INSECURE [TESTNET] (" + name + ")";
} else if (net.isTestnet) {
banner.textContent = "[TESTNET]";
} else {
banner.textContent = "DEBUG / INSECURE (" + name + ")";
}
banner.textContent = "DEBUG / INSECURE (" + name + ")";
}
}
}
// Callback to re-render the main/home view when navigating back to it.
// Set once by index.js via setRenderMain().
let _renderMain = null;
function setRenderMain(fn) {
_renderMain = fn;
}
// Push the current view onto the navigation stack so goBack() can
// return to it. Call this before any forward navigation.
function pushCurrentView() {
if (state.currentView) {
state.viewStack.push(state.currentView);
}
}
// Pop the navigation stack and show the previous view. If the stack
// is empty, fall back to the main (home) view.
function goBack() {
let target;
if (state.viewStack.length > 0) {
target = state.viewStack.pop();
} else {
target = "main";
}
if (target === "main" && _renderMain) {
_renderMain();
}
showView(target);
}
// Clear the entire navigation stack (used when resetting to root,
// e.g. after adding or deleting a wallet).
function clearViewStack() {
state.viewStack = [];
}
let flashTimer = null;
function clearFlash() {
@@ -417,10 +372,6 @@ module.exports = {
showError,
hideError,
showView,
setRenderMain,
pushCurrentView,
goBack,
clearViewStack,
showFlash,
flashCopyFeedback,
balanceLine,

View File

@@ -12,7 +12,6 @@ const {
truncateMiddle,
renderAddressHtml,
attachCopyHandlers,
pushCurrentView,
} = require("./helpers");
const { state, saveState, currentAddress } = require("../../shared/state");
const {
@@ -382,7 +381,6 @@ function init(ctx) {
renderSendTokenSelect(addr);
updateSendBalance();
resetSendValidation();
pushCurrentView();
showView("send");
});

View File

@@ -6,7 +6,6 @@ const {
formatAddressHtml,
addressTitle,
attachCopyHandlers,
goBack,
} = require("./helpers");
const { state, currentAddress, currentNetwork } = require("../../shared/state");
const QRCode = require("qrcode");
@@ -68,7 +67,11 @@ function init(ctx) {
});
$("btn-receive-back").addEventListener("click", () => {
goBack();
if (state.selectedToken) {
ctx.showAddressToken();
} else {
ctx.showAddressDetail();
}
});
}

View File

@@ -7,7 +7,6 @@ const {
escapeHtml,
renderAddressHtml,
attachCopyHandlers,
goBack,
} = require("./helpers");
const { state, currentAddress } = require("../../shared/state");
let ctx;
@@ -251,7 +250,11 @@ function init(_ctx) {
$("btn-send-back").addEventListener("click", () => {
$("send-token").classList.remove("hidden");
$("send-token-static").classList.add("hidden");
goBack();
if (state.selectedToken) {
ctx.showAddressToken();
} else {
ctx.showAddressDetail();
}
});
}

View File

@@ -1,11 +1,4 @@
const {
$,
showView,
showFlash,
escapeHtml,
goBack,
pushCurrentView,
} = require("./helpers");
const { $, showView, showFlash, escapeHtml } = require("./helpers");
const { applyTheme } = require("../theme");
const { state, saveState, currentNetwork } = require("../../shared/state");
const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks");
@@ -93,7 +86,6 @@ function renderWalletListSettings() {
container.querySelectorAll(".btn-delete-wallet").forEach((btn) => {
btn.addEventListener("click", () => {
const idx = parseInt(btn.dataset.idx, 10);
pushCurrentView();
deleteWallet.show(idx);
});
});
@@ -290,7 +282,8 @@ function init(ctx) {
);
$("btn-settings-back").addEventListener("click", () => {
goBack();
ctx.renderWalletList();
showView("main");
});
}

View File

@@ -1,4 +1,4 @@
const { $, showView, showFlash, goBack } = require("./helpers");
const { $, showView, showFlash } = require("./helpers");
const { getTopTokens } = require("../../shared/tokenList");
const { state, saveState } = require("../../shared/state");
const { lookupTokenInfo } = require("../../shared/balances");
@@ -84,7 +84,7 @@ function init(_ctx) {
ctx = _ctx;
$("btn-settings-addtoken-back").addEventListener("click", () => {
goBack();
ctx.showSettingsView();
});
$("btn-settings-addtoken-select").addEventListener("click", async () => {

View File

@@ -14,7 +14,6 @@ const {
attachCopyHandlers,
copyableHtml,
etherscanLinkHtml,
goBack,
} = require("./helpers");
const { state, currentNetwork } = require("../../shared/state");
const { formatEther, formatUnits } = require("ethers");
@@ -351,7 +350,11 @@ async function loadFullTxDetails(txHash, toAddress, isContractCall) {
function init(_ctx) {
ctx = _ctx;
$("btn-tx-back").addEventListener("click", () => {
goBack();
if (state.selectedToken) {
ctx.showAddressToken();
} else {
ctx.showAddressDetail();
}
});
}

View File

@@ -9,7 +9,6 @@ const {
attachCopyHandlers,
copyableHtml,
etherscanLinkHtml,
clearViewStack,
} = require("./helpers");
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
const { state, saveState, currentNetwork } = require("../../shared/state");
@@ -222,16 +221,10 @@ function navigateBack() {
window.close();
return;
}
// After a completed transaction, reset the navigation stack
// and go directly to the address view (token or detail).
// Use require() lazily to call show() without the ctx push wrapper.
clearViewStack();
state.viewStack.push("main");
if (state.selectedToken) {
state.viewStack.push("address");
require("./addressToken").show();
ctx.showAddressToken();
} else {
require("./addressDetail").show();
ctx.showAddressDetail();
}
}

View File

@@ -38,7 +38,6 @@ const state = {
selectedAddress: null,
selectedToken: null,
viewData: {},
viewStack: [],
};
// Return the network configuration for the currently selected network.
@@ -73,7 +72,6 @@ async function saveState() {
selectedAddress: state.selectedAddress,
selectedToken: state.selectedToken,
viewData: state.viewData,
viewStack: state.viewStack,
};
await storageApi.set({ autistmask: persisted });
}
@@ -135,7 +133,6 @@ async function loadState() {
saved.selectedAddress !== undefined ? saved.selectedAddress : null;
state.selectedToken = saved.selectedToken || null;
state.viewData = saved.viewData || {};
state.viewStack = Array.isArray(saved.viewStack) ? saved.viewStack : [];
}
}