Out of the box the wallet sent the user's addresses to Blockscout. It sent each recipient to Etherscan for its label. Two settings, "Use Blockscout" and "Check recipients on Etherscan", now gate those requests. Both are off in a new install and offered unticked on Welcome and in Settings. While one is off, the screens that relied on it say so in a plain line. Each gated request reads its setting from storage before it asks and when the answer lands. An answer that lands after the setting went off is dropped. An open popup takes a change another page saved and draws its screens again. While "Use Blockscout" is off, no address keeps an untracked token. ENS lookups ask only the RPC endpoint. Model: opus-5-5
128 lines
4.4 KiB
JavaScript
128 lines
4.4 KiB
JavaScript
// Tests for the token filtering in the Send view's token selector
|
|
// (src/popup/views/send.js).
|
|
//
|
|
// The selector decides which of the user's tokens can be spent at all, so
|
|
// over-filtering here is worse than in the history list: the asset is not
|
|
// merely hidden, it becomes unspendable through the UI. Issue #230: an
|
|
// explorer that omits holders_count was read as "zero holders" and the token
|
|
// disappeared from this list.
|
|
//
|
|
// renderSendTokenSelect only ever touches getElementById, createElement,
|
|
// innerHTML, value, textContent and appendChild, so a small stub document is
|
|
// enough to drive it; the real DOM behaviour of the view is covered by
|
|
// tests/e2e/run.js.
|
|
|
|
globalThis.chrome = {
|
|
storage: { local: { get: async () => ({}), set: async () => {} } },
|
|
};
|
|
|
|
const { state } = require("../src/shared/state");
|
|
const { renderSendTokenSelect } = require("../src/popup/views/send");
|
|
|
|
const USDC_CONTRACT = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
|
const NOVEL_TOKEN = "0x1111111111111111111111111111111111111111";
|
|
|
|
let select;
|
|
|
|
function installStubDocument() {
|
|
select = { innerHTML: "", children: [] };
|
|
select.appendChild = (child) => select.children.push(child);
|
|
const elements = {
|
|
"send-token": select,
|
|
"send-tokens-note": { classList: { toggle: () => {} } },
|
|
};
|
|
globalThis.document = {
|
|
getElementById: (id) => elements[id] || null,
|
|
createElement: () => ({ value: "", textContent: "" }),
|
|
};
|
|
}
|
|
|
|
// The symbols offered for sending, excluding the hardcoded ETH option that
|
|
// renderSendTokenSelect writes straight into innerHTML.
|
|
function offeredTokens() {
|
|
return select.children.map((opt) => opt.value.toLowerCase());
|
|
}
|
|
|
|
function tokenBalance(overrides) {
|
|
return {
|
|
address: NOVEL_TOKEN,
|
|
symbol: "SPAMTKN",
|
|
decimals: 18,
|
|
balance: "12.5",
|
|
holders: 50000,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function render(tokenBalances) {
|
|
installStubDocument();
|
|
renderSendTokenSelect({ address: "0x" + "a".repeat(40), tokenBalances });
|
|
}
|
|
|
|
beforeEach(() => {
|
|
state.fraudContracts = [];
|
|
state.hideLowHolderTokens = true;
|
|
});
|
|
|
|
describe("the low-holder rule in the send token selector", () => {
|
|
test("ETH is always offered", () => {
|
|
render([]);
|
|
expect(select.innerHTML).toBe('<option value="ETH">ETH</option>');
|
|
expect(offeredTokens()).toEqual([]);
|
|
});
|
|
|
|
test("a token with plenty of holders is offered", () => {
|
|
render([tokenBalance()]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
test("a token reporting zero holders is withheld", () => {
|
|
render([tokenBalance({ holders: 0 })]);
|
|
expect(offeredTokens()).toEqual([]);
|
|
});
|
|
|
|
test("boundary: 999 holders is withheld, 1000 is offered", () => {
|
|
render([tokenBalance({ holders: 999 })]);
|
|
expect(offeredTokens()).toEqual([]);
|
|
render([tokenBalance({ holders: 1000 })]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
// Issue #230: an unknown holder count must not read as zero. A token the
|
|
// user demonstrably holds — it has a balance — cannot be made unspendable
|
|
// by a field the block explorer failed to report.
|
|
test("a token whose holder count is unknown is still offered", () => {
|
|
render([tokenBalance({ holders: null })]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
test("a token balance carrying no holders field at all is offered", () => {
|
|
const t = tokenBalance();
|
|
delete t.holders;
|
|
render([t]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
test("the rule is bypassed entirely when the setting is off", () => {
|
|
state.hideLowHolderTokens = false;
|
|
render([tokenBalance({ holders: 0 })]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
});
|
|
|
|
describe("the other send-selector rules are unaffected", () => {
|
|
test("a token spoofing a known symbol from a wrong address is withheld", () => {
|
|
render([
|
|
tokenBalance({ symbol: "USDC", holders: null }),
|
|
tokenBalance({ address: USDC_CONTRACT, symbol: "USDC" }),
|
|
]);
|
|
expect(offeredTokens()).toEqual([USDC_CONTRACT.toLowerCase()]);
|
|
});
|
|
|
|
test("a blocklisted fraud contract is withheld even with an unknown count", () => {
|
|
state.fraudContracts = [NOVEL_TOKEN.toUpperCase()];
|
|
render([tokenBalance({ holders: null })]);
|
|
expect(offeredTokens()).toEqual([]);
|
|
});
|
|
});
|