chore: the native token's label follows the network (closes #372)
check / check (push) Failing after 2s
e2e / e2e-chrome (push) Failing after 1s
e2e / e2e-firefox (push) Failing after 1s

networks.js gives each network a nativeCurrency (ETH, SepoliaETH) and nothing
read it: every screen wrote ETH. The wallet's balances and the Send and
confirmation screens now use the active network's. A transaction's figures use
the network its chain id names, through nativeCurrencyByChainId(): the
approval value and fee, the wait, success and error screens, history entries,
the detail screen and the fee-limit refusal, so a site switching networks
cannot make one read as another network's coin. The "ETH" that selectedToken
and txInfo.token hold is the native token's id and is unchanged. A token
reporting any network's nativeCurrency is a spoof, and the detail screen calls
an entry a token transfer when it has a token contract.

Model: opus-5-5
This commit was merged in pull request #442.
This commit is contained in:
2026-10-05 01:26:04 +02:00
parent 2b97aae04a
commit e3790c5da4
20 changed files with 769 additions and 83 deletions
+66 -13
View File
@@ -1219,7 +1219,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
test("queries only the two Blockscout endpoints for the address", async () => {
respondWith([], []);
await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
expect(debugFetch).toHaveBeenCalledTimes(2);
const urls = debugFetch.mock.calls.map((c) => c[0]);
expect(urls).toContain(
@@ -1283,7 +1283,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
],
);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
expect(txs).toHaveLength(1);
const merged = txs[0];
// The received leg (the swap output) supplies the display amount.
@@ -1320,7 +1320,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
],
);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
expect(txs).toHaveLength(1);
expect(txs[0].symbol).toBe("USDC");
expect(txs[0].value).toBe("1500.5000");
@@ -1346,7 +1346,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
});
respondWith([], [leg("1000000"), leg("2000000")]);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
expect(txs).toHaveLength(1);
// Keyed by hash plus contract, so the later leg wins.
expect(txs[0].exactValue).toBe("2.0");
@@ -1386,7 +1386,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
],
);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
expect(txs.map((t) => t.symbol).sort()).toEqual(["USDC", "WETH"]);
});
@@ -1427,12 +1427,13 @@ describe("fetchRecentTransactions merge and dedup", () => {
],
);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
expect(txs).toHaveLength(1);
expect(txs[0].symbol).toBe("USDC");
expect(txs[0].exactValue).toBe("1.0");
expect(txs[0].direction).toBe("sent");
expect(txs[0].contractAddress).toBe(USDC_CONTRACT);
expect(txs[0].chainId).toBe("0x1");
// The surviving row is the token row, and the filters keep it.
const kept = filterTransactions(txs, filters()).transactions;
expect(kept).toHaveLength(1);
@@ -1452,10 +1453,46 @@ describe("fetchRecentTransactions merge and dedup", () => {
});
respondWith([item(6), item(8), item(7)], []);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, 2);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1", 2);
expect(txs.map((t) => t.blockNumber)).toEqual([21000008, 21000007]);
});
// https://git.eeqj.de/sneak/AutistMask/issues/372: the caller hands in
// the chain id of the network the explorer serves. Every entry carries
// it, and a native entry is labelled with that network's nativeCurrency.
test("a native entry is labelled by the chain id handed in", async () => {
respondWith(
[
{
hash: "0x" + "a".repeat(64),
block_number: 21000090,
timestamp: TS,
from: { hash: ORDINARY_PEER },
to: { hash: VICTIM, is_contract: false },
value: "10000000000000000",
method: null,
status: "ok",
},
],
[],
);
const sepolia = await fetchRecentTransactions(
VICTIM,
BLOCKSCOUT,
"0xaa36a7",
);
expect(sepolia[0].symbol).toBe("SepoliaETH");
expect(sepolia[0].value).toBe("0.0100");
expect(sepolia[0].chainId).toBe("0xaa36a7");
const mainnet = await fetchRecentTransactions(
VICTIM,
BLOCKSCOUT,
"0x1",
);
expect(mainnet[0].symbol).toBe("ETH");
expect(mainnet[0].chainId).toBe("0x1");
});
test("the fake token transfer survives fetching and is then filtered", async () => {
respondWith(
[],
@@ -1476,7 +1513,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
],
);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
expect(txs).toHaveLength(1);
expect(txs[0].contractAddress).toBe(FAKE_ETH_CONTRACT);
expect(txs[0].holders).toBe(0);
@@ -1515,19 +1552,31 @@ describe("fetchRecentTransactions merge and dedup", () => {
test("an omitted holders_count parses to null", async () => {
respondWith([], spamTransferWithToken(OMITTED));
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(
VICTIM,
BLOCKSCOUT,
"0x1",
);
expect(txs[0].holders).toBeNull();
});
test("a null holders_count parses to null", async () => {
respondWith([], spamTransferWithToken(NULLED));
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(
VICTIM,
BLOCKSCOUT,
"0x1",
);
expect(txs[0].holders).toBeNull();
});
test("the transfer survives the low-holder filter", async () => {
respondWith([], spamTransferWithToken(OMITTED));
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(
VICTIM,
BLOCKSCOUT,
"0x1",
);
expect(filterTransactions(txs, filters()).transactions).toEqual(
txs,
);
@@ -1539,7 +1588,11 @@ describe("fetchRecentTransactions merge and dedup", () => {
// holder count is the only rule that can catch it.
test('a reported holders_count of "0" still parses to 0 and is filtered', async () => {
respondWith([], spamTransferWithToken(ZERO));
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
const txs = await fetchRecentTransactions(
VICTIM,
BLOCKSCOUT,
"0x1",
);
expect(txs[0].holders).toBe(0);
expect(filterTransactions(txs, filters()).transactions).toEqual([]);
});
@@ -1555,7 +1608,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
},
}));
await expect(
fetchRecentTransactions(VICTIM, BLOCKSCOUT),
fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1"),
).resolves.toEqual([]);
});