Compare commits

..

1 Commits

Author SHA1 Message Date
clawbot
bc4e78c466 fix: consistent token contract display on confirm-tx page
All checks were successful
check / check (push) Successful in 23s
The confirm-tx page displayed token contract addresses as plain text,
while addressToken used a styled container (bg-hover rounded-md) with
color dot, copy-on-click, etherscan link, and token metadata.

Changes:
- confirm-tx HTML: use bg-hover rounded-md container matching addressToken
- confirm-tx JS: render contract with addressDotHtml, copy-on-click span,
  etherscan link, and optional token name/symbol metadata
- send.js: pass tokenName to confirmTx for richer contract display
- Add click-to-copy handler for contract address on confirm-tx

Closes #70
2026-02-28 12:06:25 -08:00
7 changed files with 46 additions and 200 deletions

View File

@@ -305,26 +305,6 @@
> >
+ Token + Token
</button> </button>
<div class="relative">
<button
id="btn-more-menu"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
aria-label="More actions"
>
&middot;&middot;&middot;
</button>
<div
id="more-menu-dropdown"
class="hidden absolute right-0 top-full mt-1 border border-border bg-bg z-50 whitespace-nowrap py-1"
>
<button
id="btn-export-privkey"
class="block w-full text-left px-4 py-1.5 text-xs font-light text-muted hover:bg-hover hover:text-fg cursor-pointer"
>
Export Private Key
</button>
</div>
</div>
</div> </div>
<!-- transactions --> <!-- transactions -->
@@ -338,60 +318,6 @@
</div> </div>
</div> </div>
<!-- ============ EXPORT PRIVATE KEY VIEW ============ -->
<div id="view-export-privkey" class="view hidden">
<button
id="btn-export-privkey-back"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mb-2"
>
&lt; Back
</button>
<div
id="export-privkey-jazzicon"
class="flex justify-center mt-1 mb-3"
></div>
<h2 class="font-bold mb-1">Export Private Key</h2>
<p class="text-xs mb-1" id="export-privkey-title"></p>
<p class="text-xs mb-3">
<span id="export-privkey-dot"></span>
<span
id="export-privkey-address"
class="cursor-pointer"
title="Click to copy"
></span>
</p>
<p class="text-xs mb-3 text-muted">
Warning: anyone with this private key can access and
transfer all funds from this address. Never share it.
</p>
<div
id="export-privkey-flash"
class="text-xs mb-2 hidden"
></div>
<div id="export-privkey-password-section" class="mb-2">
<label class="block mb-1">Password</label>
<input
type="password"
id="export-privkey-password"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
placeholder="Enter your password to continue"
/>
<button
id="btn-export-privkey-confirm"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mt-2"
>
Reveal
</button>
</div>
<div id="export-privkey-result" class="hidden">
<div
id="export-privkey-value"
class="bg-danger-well rounded p-2 font-mono text-xs break-all cursor-pointer mb-1"
title="Click to copy"
></div>
</div>
</div>
<!-- ============ ADDRESS-TOKEN DETAIL VIEW ============ --> <!-- ============ ADDRESS-TOKEN DETAIL VIEW ============ -->
<div id="view-address-token" class="view hidden"> <div id="view-address-token" class="view hidden">
<button <button
@@ -537,12 +463,12 @@
</div> </div>
<!-- ERC-20 token contract (hidden for ETH) --> <!-- ERC-20 token contract (hidden for ETH) -->
<div id="confirm-token-section" class="mb-3 hidden"> <div
<div class="text-xs text-muted mb-1">Token contract</div> id="confirm-token-section"
<div class="bg-hover rounded-md mx-1 p-3 mb-3 text-xs hidden"
id="confirm-token-contract" >
class="text-xs break-all" <div class="font-bold mb-2">Token Contract</div>
></div> <div id="confirm-token-contract" class="break-all"></div>
</div> </div>
<div class="mb-3"> <div class="mb-3">

View File

@@ -74,7 +74,6 @@ const RESTORABLE_VIEWS = new Set([
"receive", "receive",
"settings", "settings",
"settings-addtoken", "settings-addtoken",
"confirm-tx",
"transaction", "transaction",
"success-tx", "success-tx",
"error-tx", "error-tx",
@@ -128,13 +127,6 @@ function restoreView() {
case "settings-addtoken": case "settings-addtoken":
settingsAddToken.show(); settingsAddToken.show();
break; break;
case "confirm-tx":
if (state.viewData && state.viewData.pendingTx) {
confirmTx.restore();
} else {
fallbackView();
}
break;
case "transaction": case "transaction":
if (state.viewData && state.viewData.tx) { if (state.viewData && state.viewData.tx) {
transactionDetail.render(); transactionDetail.render();

View File

@@ -11,7 +11,6 @@
--color-border-light: #cccccc; --color-border-light: #cccccc;
--color-hover: #eeeeee; --color-hover: #eeeeee;
--color-well: #f5f5f5; --color-well: #f5f5f5;
--color-danger-well: #fef2f2;
--color-section: #dddddd; --color-section: #dddddd;
} }

View File

@@ -18,8 +18,6 @@ const { resolveEnsNames } = require("../../shared/ens");
const { updateSendBalance, renderSendTokenSelect } = require("./send"); const { updateSendBalance, renderSendTokenSelect } = require("./send");
const { log } = require("../../shared/log"); const { log } = require("../../shared/log");
const makeBlockie = require("ethereum-blockies-base64"); const makeBlockie = require("ethereum-blockies-base64");
const { decryptWithPassword } = require("../../shared/vault");
const { getSignerForAddress } = require("../../shared/wallet");
let ctx; let ctx;
@@ -267,102 +265,6 @@ function init(_ctx) {
}); });
$("btn-add-token").addEventListener("click", ctx.showAddTokenView); $("btn-add-token").addEventListener("click", ctx.showAddTokenView);
// More menu dropdown
const moreBtn = $("btn-more-menu");
const moreDropdown = $("more-menu-dropdown");
moreBtn.addEventListener("click", (e) => {
e.stopPropagation();
const isOpen = !moreDropdown.classList.toggle("hidden");
moreBtn.classList.toggle("bg-fg", isOpen);
moreBtn.classList.toggle("text-bg", isOpen);
});
document.addEventListener("click", () => {
moreDropdown.classList.add("hidden");
moreBtn.classList.remove("bg-fg", "text-bg");
});
moreDropdown.addEventListener("click", (e) => {
e.stopPropagation();
});
$("btn-export-privkey").addEventListener("click", () => {
moreDropdown.classList.add("hidden");
moreBtn.classList.remove("bg-fg", "text-bg");
const wallet = state.wallets[state.selectedWallet];
const addr = wallet.addresses[state.selectedAddress];
const blockieEl = $("export-privkey-jazzicon");
blockieEl.innerHTML = "";
const bImg = document.createElement("img");
bImg.src = makeBlockie(addr.address);
bImg.width = 48;
bImg.height = 48;
bImg.style.imageRendering = "pixelated";
bImg.style.borderRadius = "50%";
blockieEl.appendChild(bImg);
$("export-privkey-title").textContent =
wallet.name + " \u2014 Address " + (state.selectedAddress + 1);
$("export-privkey-dot").innerHTML = addressDotHtml(addr.address);
$("export-privkey-address").textContent = addr.address;
$("export-privkey-address").dataset.full = addr.address;
$("export-privkey-password").value = "";
$("export-privkey-flash").classList.add("hidden");
$("export-privkey-flash").textContent = "";
$("export-privkey-password-section").classList.remove("hidden");
$("export-privkey-result").classList.add("hidden");
$("export-privkey-value").textContent = "";
showView("export-privkey");
});
$("btn-export-privkey-confirm").addEventListener("click", async () => {
const password = $("export-privkey-password").value;
if (!password) {
$("export-privkey-flash").textContent = "Password is required.";
$("export-privkey-flash").classList.remove("hidden");
return;
}
const wallet = state.wallets[state.selectedWallet];
try {
const secret = await decryptWithPassword(
wallet.encryptedSecret,
password,
);
const signer = getSignerForAddress(
wallet,
state.selectedAddress,
secret,
);
const privateKey = signer.privateKey;
$("export-privkey-password-section").classList.add("hidden");
$("export-privkey-value").textContent = privateKey;
$("export-privkey-result").classList.remove("hidden");
$("export-privkey-flash").classList.add("hidden");
} catch {
$("export-privkey-flash").textContent = "Wrong password.";
$("export-privkey-flash").classList.remove("hidden");
}
});
$("export-privkey-value").addEventListener("click", () => {
const key = $("export-privkey-value").textContent;
if (key) {
navigator.clipboard.writeText(key);
showFlash("Copied!");
}
});
$("export-privkey-address").addEventListener("click", () => {
const full = $("export-privkey-address").dataset.full;
if (full) {
navigator.clipboard.writeText(full);
showFlash("Copied!");
}
});
$("btn-export-privkey-back").addEventListener("click", () => {
$("export-privkey-value").textContent = "";
$("export-privkey-password").value = "";
show();
});
} }
module.exports = { init, show }; module.exports = { init, show };

View File

@@ -38,13 +38,6 @@ const EXT_ICON =
let pendingTx = null; let pendingTx = null;
function restore() {
const d = state.viewData;
if (d && d.pendingTx) {
show(d.pendingTx);
}
}
function etherscanTokenLink(address) { function etherscanTokenLink(address) {
return `https://etherscan.io/token/${address}`; return `https://etherscan.io/token/${address}`;
} }
@@ -102,10 +95,22 @@ function show(txInfo) {
// Token contract section (ERC-20 only) // Token contract section (ERC-20 only)
const tokenSection = $("confirm-token-section"); const tokenSection = $("confirm-token-section");
if (isErc20) { if (isErc20) {
const dot = addressDotHtml(txInfo.token);
const link = etherscanTokenLink(txInfo.token); const link = etherscanTokenLink(txInfo.token);
$("confirm-token-contract").innerHTML = $("confirm-token-contract").innerHTML =
escapeHtml(txInfo.token) + `<div class="flex items-center">` +
` <a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`; dot +
`<span class="break-all underline decoration-dashed cursor-pointer" id="confirm-token-contract-copy" data-copy="${escapeHtml(txInfo.token)}">${escapeHtml(txInfo.token)}</span>` +
`<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>` +
`</div>`;
if (txInfo.tokenName) {
$("confirm-token-contract").innerHTML +=
`<div class="mt-1"><span class="text-muted">Name:</span> ${escapeHtml(txInfo.tokenName)}</div>`;
}
if (txInfo.tokenSymbol) {
$("confirm-token-contract").innerHTML +=
`<div class="mt-1"><span class="text-muted">Symbol:</span> ${escapeHtml(txInfo.tokenSymbol)}</div>`;
}
tokenSection.classList.remove("hidden"); tokenSection.classList.remove("hidden");
} else { } else {
tokenSection.classList.add("hidden"); tokenSection.classList.add("hidden");
@@ -224,7 +229,6 @@ function show(txInfo) {
// Gas estimate — show placeholder then fetch async // Gas estimate — show placeholder then fetch async
$("confirm-fee").classList.remove("hidden"); $("confirm-fee").classList.remove("hidden");
$("confirm-fee-amount").textContent = "Estimating..."; $("confirm-fee-amount").textContent = "Estimating...";
state.viewData = { pendingTx: txInfo };
showView("confirm-tx"); showView("confirm-tx");
estimateGas(txInfo); estimateGas(txInfo);
@@ -281,6 +285,13 @@ function hidePasswordModal() {
} }
function init(ctx) { function init(ctx) {
$("confirm-token-section").addEventListener("click", (e) => {
const copyEl = e.target.closest("[data-copy]");
if (copyEl) {
navigator.clipboard.writeText(copyEl.dataset.copy);
}
});
$("btn-confirm-send").addEventListener("click", () => { $("btn-confirm-send").addEventListener("click", () => {
showPasswordModal(); showPasswordModal();
}); });
@@ -355,4 +366,4 @@ function init(ctx) {
}); });
} }
module.exports = { init, show, restore }; module.exports = { init, show };

View File

@@ -31,7 +31,6 @@ const VIEWS = [
"approve-site", "approve-site",
"approve-tx", "approve-tx",
"approve-sign", "approve-sign",
"export-privkey",
]; ];
function $(id) { function $(id) {

View File

@@ -10,7 +10,11 @@ const {
const { state, currentAddress } = require("../../shared/state"); const { state, currentAddress } = require("../../shared/state");
let ctx; let ctx;
const { getProvider } = require("../../shared/balances"); const { getProvider } = require("../../shared/balances");
const { KNOWN_SYMBOLS, resolveSymbol } = require("../../shared/tokenList"); const {
KNOWN_SYMBOLS,
TOKEN_BY_ADDRESS,
resolveSymbol,
} = require("../../shared/tokenList");
const EXT_ICON = const EXT_ICON =
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` + `<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
@@ -124,6 +128,7 @@ function init(_ctx) {
let tokenSymbol = null; let tokenSymbol = null;
let tokenBalance = null; let tokenBalance = null;
let tokenName = null;
if (token !== "ETH") { if (token !== "ETH") {
const tb = (addr.tokenBalances || []).find( const tb = (addr.tokenBalances || []).find(
(t) => t.address.toLowerCase() === token.toLowerCase(), (t) => t.address.toLowerCase() === token.toLowerCase(),
@@ -134,6 +139,17 @@ function init(_ctx) {
state.trackedTokens, state.trackedTokens,
); );
tokenBalance = tb ? tb.balance || "0" : "0"; tokenBalance = tb ? tb.balance || "0" : "0";
// Resolve token name from balances, tracked tokens, or known list
const lower = token.toLowerCase();
tokenName =
(tb && tb.name) ||
(
(state.trackedTokens || []).find(
(t) => t.address.toLowerCase() === lower,
) || {}
).name ||
(TOKEN_BY_ADDRESS.get(lower) || {}).name ||
null;
} }
ctx.showConfirmTx({ ctx.showConfirmTx({
@@ -145,6 +161,7 @@ function init(_ctx) {
balance: addr.balance, balance: addr.balance,
tokenSymbol: tokenSymbol, tokenSymbol: tokenSymbol,
tokenBalance: tokenBalance, tokenBalance: tokenBalance,
tokenName: tokenName,
}); });
}); });