A balance refresh that returns now draws the balances on AddressDetail or AddressToken again when that screen is up, not only the wallet list; their transaction lists are left alone. Every add path on AddToken and SettingsAddToken tracks a token through one step, trackToken(), which also lists it in the new stored field tokensNotFetched: its line reads `quantity unknown` instead of 0.0000 until a refresh that started with it tracked has fetched every address's token balances, even if the popup closes first. To tell an address whose token balances the explorer did not return, the refresh takes them off its copy of the wallets before fetching. Model: opus-5-5
121 lines
4.6 KiB
JavaScript
121 lines
4.6 KiB
JavaScript
const {
|
|
$,
|
|
showView,
|
|
showFlash,
|
|
escapeHtml,
|
|
goBack,
|
|
trackToken,
|
|
} = require("./helpers");
|
|
const { getTopTokens } = require("../../shared/tokenList");
|
|
const { state, saveState, currentNetwork } = require("../../shared/state");
|
|
const { lookupTokenInfo } = require("../../shared/balances");
|
|
const { isScamAddress } = require("../../shared/scamlist");
|
|
const { log } = require("../../shared/log");
|
|
|
|
function show() {
|
|
$("add-token-address").value = "";
|
|
$("add-token-info").textContent = "";
|
|
$("add-token-info").style.visibility = "hidden";
|
|
// The bundled list holds Ethereum Mainnet contracts, which name no token
|
|
// on any other network.
|
|
$("add-token-common").classList.toggle(
|
|
"hidden",
|
|
state.networkId !== "mainnet",
|
|
);
|
|
const list = $("common-token-list");
|
|
list.innerHTML = getTopTokens(25)
|
|
.map(
|
|
(t) =>
|
|
`<button class="common-token border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs" data-address="${escapeHtml(t.address)}" data-symbol="${escapeHtml(t.symbol)}" data-decimals="${escapeHtml(t.decimals)}">${escapeHtml(t.symbol)}</button>`,
|
|
)
|
|
.join("");
|
|
list.querySelectorAll(".common-token").forEach((btn) => {
|
|
btn.addEventListener("click", () => {
|
|
$("add-token-address").value = btn.dataset.address;
|
|
});
|
|
});
|
|
showView("add-token");
|
|
}
|
|
|
|
function init(ctx) {
|
|
$("btn-add-token-confirm").addEventListener("click", async () => {
|
|
const contractAddr = $("add-token-address").value.trim();
|
|
if (!contractAddr || !contractAddr.startsWith("0x")) {
|
|
showFlash("Enter a valid contract address starting with 0x.");
|
|
return;
|
|
}
|
|
const already = state.trackedTokens.find(
|
|
(t) => t.address.toLowerCase() === contractAddr.toLowerCase(),
|
|
);
|
|
if (already) {
|
|
showFlash(already.symbol + " is already being tracked.");
|
|
return;
|
|
}
|
|
if (isScamAddress(contractAddr)) {
|
|
showFlash("This address is on a known scam/fraud list.");
|
|
return;
|
|
}
|
|
const infoEl = $("add-token-info");
|
|
infoEl.textContent = "Looking up token...";
|
|
infoEl.style.visibility = "visible";
|
|
log.debugf("Looking up token contract", contractAddr);
|
|
// The network the lookup asks, which Settings may switch away from
|
|
// before it answers, as for a transaction in confirmTx.js.
|
|
const chainId = currentNetwork().chainId;
|
|
try {
|
|
const info = await lookupTokenInfo(
|
|
contractAddr,
|
|
state.rpcUrl,
|
|
state.networkId,
|
|
ctx.pageClosed,
|
|
);
|
|
// A contract address names a token on one network only, so an
|
|
// answer after a switch is not added to the new network's list.
|
|
if (currentNetwork().chainId !== chainId) {
|
|
showFlash(
|
|
"The active network changed before the lookup finished," +
|
|
" so the token was not added.",
|
|
);
|
|
return;
|
|
}
|
|
log.infof("Adding token", info.symbol, contractAddr);
|
|
trackToken({
|
|
address: contractAddr,
|
|
symbol: info.symbol,
|
|
decimals: info.decimals,
|
|
name: info.name,
|
|
});
|
|
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();
|
|
} catch (e) {
|
|
// Cancelled by the popup closing, not failed: see pageClosed in
|
|
// src/popup/index.js.
|
|
if (ctx.pageClosed.aborted) return;
|
|
const detail = e.shortMessage || e.message || String(e);
|
|
log.errorf("Adding token failed for", contractAddr, detail);
|
|
// lookupTokenInfo() rejects a contract with a one-line message
|
|
// starting "Not a valid ERC-20 token". Any other error, such as a
|
|
// failed save, can be far longer, so it is only logged.
|
|
showFlash(
|
|
detail.startsWith("Not a valid ERC-20 token")
|
|
? detail
|
|
: "Could not add the token.",
|
|
);
|
|
infoEl.textContent = "";
|
|
infoEl.style.visibility = "hidden";
|
|
}
|
|
});
|
|
|
|
$("btn-add-token-back").addEventListener("click", () => {
|
|
goBack();
|
|
});
|
|
}
|
|
|
|
module.exports = { init, show };
|