';
return;
}
let html = "";
state.wallets.forEach((wallet, idx) => {
const name = escapeHtml(wallet.name || "Wallet " + (idx + 1));
html += `
`;
html += `${name}`;
html += ``;
// Key and xprv wallets have no recovery phrase, so they are never
// offered the action at all.
if (walletHasRecoveryPhrase(wallet)) {
html += ``;
}
html += ``;
html += ``;
html += `
`;
});
container.innerHTML = html;
container.querySelectorAll(".btn-delete-wallet").forEach((btn) => {
btn.addEventListener("click", () => {
const idx = parseInt(btn.dataset.idx, 10);
pushCurrentView();
deleteWallet.show(idx);
});
});
container.querySelectorAll(".btn-show-phrase").forEach((btn) => {
btn.addEventListener("click", () => {
const idx = parseInt(btn.dataset.idx, 10);
// No pushCurrentView() here: showPhrase.show() refuses
// non-HD wallets and pushes only when it navigates.
showPhrase.show(idx);
});
});
// Inline rename on click
container.querySelectorAll(".settings-wallet-name").forEach((span) => {
span.addEventListener("click", () => {
const idx = parseInt(span.dataset.idx, 10);
const wallet = state.wallets[idx];
const input = document.createElement("input");
input.type = "text";
input.className =
"border border-border p-0 text-xs bg-bg text-fg w-full";
input.value = wallet.name || "Wallet " + (idx + 1);
span.replaceWith(input);
input.focus();
input.select();
const finish = async () => {
const val = input.value.trim();
if (val && val !== wallet.name) {
wallet.name = val;
await saveState();
}
renderWalletListSettings();
};
input.addEventListener("blur", finish);
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") input.blur();
if (e.key === "Escape") {
input.value = wallet.name || "Wallet " + (idx + 1);
input.blur();
}
});
});
});
}
function show() {
$("settings-rpc").value = state.rpcUrl;
$("settings-blockscout").value = state.blockscoutUrl;
const networkSelect = $("settings-network");
if (networkSelect) {
networkSelect.value = state.networkId;
}
renderTrackedTokens();
renderSiteLists();
renderWalletListSettings();
// Populate About well
$("about-license").textContent = BUILD_LICENSE;
// Show only the name part of the author field (strip email)
const authorName = BUILD_AUTHOR.replace(/\s*<[^>]+>/, "");
$("about-author").textContent = authorName;
$("about-version").textContent = BUILD_VERSION;
$("about-release-date").textContent = BUILD_DATE;
$("about-commit-link").textContent = BUILD_COMMIT;
$("about-commit-link").href = GITEA_COMMIT_URL;
// Reset version click counter each time settings opens
versionClickCount = 0;
// Show debug well if debug mode is already enabled
const debugWell = $("settings-debug-well");
if (state.debugMode) {
debugWell.style.display = "";
} else {
debugWell.style.display = "none";
}
$("settings-debug-mode").checked = state.debugMode;
showView("settings");
}
function renderSiteLists() {
renderSiteList(
"settings-allowed-sites",
state.allowedSites,
"allowedSites",
);
renderSiteList("settings-denied-sites", state.deniedSites, "deniedSites");
}
function init(ctx) {
deleteWallet.init(ctx);
showPhrase.init();
$("btn-save-rpc").addEventListener("click", async () => {
const url = $("settings-rpc").value.trim();
if (!url) {
showFlash("Please enter an RPC URL.");
return;
}
showFlash("Testing endpoint...");
try {
const resp = await debugFetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_chainId",
params: [],
}),
});
const json = await resp.json();
if (json.error) {
log.errorf("RPC validation error:", json.error);
showFlash("Endpoint returned error: " + json.error.message);
return;
}
const net = currentNetwork();
if (json.result !== net.chainId) {
showFlash(
"Wrong network (expected " +
net.name +
", got chain " +
json.result +
").",
);
return;
}
} catch (e) {
log.errorf("RPC validation fetch failed:", e.message);
showFlash("Could not reach endpoint.");
return;
}
state.rpcUrl = url;
await saveState();
showFlash("Saved.");
});
$("btn-save-blockscout").addEventListener("click", async () => {
const url = $("settings-blockscout").value.trim();
if (!url) {
showFlash("Please enter a Blockscout API URL.");
return;
}
showFlash("Testing endpoint...");
try {
const resp = await debugFetch(url + "/stats");
if (!resp.ok) {
showFlash("Endpoint returned HTTP " + resp.status + ".");
return;
}
} catch (e) {
log.errorf("Blockscout validation failed:", e.message);
showFlash("Could not reach endpoint.");
return;
}
state.blockscoutUrl = url;
await saveState();
showFlash("Saved.");
});
const networkSelect = $("settings-network");
if (networkSelect) {
networkSelect.addEventListener("change", async () => {
const newId = networkSelect.value;
const net = await onChainSwitch(newId);
$("settings-rpc").value = state.rpcUrl;
$("settings-blockscout").value = state.blockscoutUrl;
showFlash("Switched to " + net.name + ".");
});
}
$("settings-show-zero-balances").checked = state.showZeroBalanceTokens;
$("settings-show-zero-balances").addEventListener("change", async () => {
state.showZeroBalanceTokens = $("settings-show-zero-balances").checked;
await saveState();
});
$("settings-theme").value = state.theme;
$("settings-theme").addEventListener("change", async () => {
state.theme = $("settings-theme").value;
await saveState();
applyTheme(state.theme);
});
$("settings-hide-spoofed-symbols").checked = state.hideSpoofedSymbols;
$("settings-hide-spoofed-symbols").addEventListener("change", async () => {
state.hideSpoofedSymbols = $("settings-hide-spoofed-symbols").checked;
await saveState();
});
$("settings-hide-low-holders").checked = state.hideLowHolderTokens;
$("settings-hide-low-holders").addEventListener("change", async () => {
state.hideLowHolderTokens = $("settings-hide-low-holders").checked;
await saveState();
});
$("settings-hide-fraud-contracts").checked = state.hideFraudContracts;
$("settings-hide-fraud-contracts").addEventListener("change", async () => {
state.hideFraudContracts = $("settings-hide-fraud-contracts").checked;
await saveState();
});
$("settings-hide-dust").checked = state.hideDustTransactions;
$("settings-hide-dust").addEventListener("change", async () => {
state.hideDustTransactions = $("settings-hide-dust").checked;
await saveState();
});
$("settings-dust-threshold").value = state.dustThresholdGwei;
$("settings-dust-threshold").addEventListener("change", async () => {
const val = parseDustThresholdGwei($("settings-dust-threshold").value);
// Rejected input is never coerced. The field is put back to the
// stored threshold so it never shows a value the wallet is not
// using, and the message says what the field wants so the snap-back
// is explained rather than silent.
if (val === null) {
showFlash(DUST_THRESHOLD_MESSAGE);
} else {
state.dustThresholdGwei = val;
await saveState();
}
$("settings-dust-threshold").value = state.dustThresholdGwei;
});
$("settings-utc-timestamps").checked = state.utcTimestamps;
$("settings-utc-timestamps").addEventListener("change", async () => {
state.utcTimestamps = $("settings-utc-timestamps").checked;
await saveState();
});
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
$("btn-settings-add-token").addEventListener(
"click",
ctx.showSettingsAddTokenView,
);
// Bright saturated colors for easter egg flashes (clicks 6–10)
const easterEggColors = [
"#ff0055", // hot pink
"#00cc44", // vivid green
"#3366ff", // electric blue
"#ff9900", // bright orange
"#aa00ff", // vivid purple
];
// Easter egg: click version 10 times to reveal the debug well.
// Each click does a copy-flash animation. After 5 clicks, each
// additional click flashes a different bright saturated color.
$("about-version").addEventListener("click", () => {
versionClickCount++;
clearTimeout(versionClickTimer);
// Reset counter if user stops clicking for 3 seconds
versionClickTimer = setTimeout(() => {
versionClickCount = 0;
}, 3000);
const el = $("about-version");
if (versionClickCount > 5) {
// Colored flash for clicks 6–10
const colorIdx = versionClickCount - 6;
const color = easterEggColors[colorIdx % easterEggColors.length];
el.classList.remove("copy-flash-fade");
el.style.backgroundColor = color;
el.style.color = "#ffffff";
setTimeout(() => {
el.style.backgroundColor = "";
el.style.color = "";
el.classList.add("copy-flash-fade");
setTimeout(() => {
el.classList.remove("copy-flash-fade");
}, 275);
}, 75);
} else {
// Standard copy-flash for clicks 1–5
flashCopyFeedback(el);
}
if (versionClickCount >= 10) {
versionClickCount = 0;
clearTimeout(versionClickTimer);
$("settings-debug-well").style.display = "";
}
});
// Debug mode toggle — update runtime flag, persist, and re-render banner
$("settings-debug-mode").addEventListener("change", async () => {
state.debugMode = $("settings-debug-mode").checked;
setRuntimeDebug(state.debugMode);
await saveState();
updateDebugBanner(state.currentView);
});
// Sync runtime debug flag on init
setRuntimeDebug(state.debugMode);
$("btn-settings-back").addEventListener("click", () => {
goBack();
});
}
module.exports = { init, show, renderSiteLists };