1 Commits
Author SHA1 Message Date
sneak c2078ff736 fix: two pages switching networks no longer lose tracked tokens (closes #701)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
A page that had not yet taken another page's network switch could lose
tracked tokens in two ways.

- Its save of an added or removed token wrote its own network's list as
  the new network's list. saveState() now keeps the stored list. It
  makes the add or removal in the list of the page's own network, which
  the stored record keeps in networkTrackedTokens.
- Its own switch in Settings wrote its old lists of tracked tokens per
  network over the stored ones. onChainSwitch() now first takes the
  stored switch with takeStoredNetwork(), as followStoredSettings() does,
  and then switches from there.

Model: opus-5-5
2026-10-27 20:35:16 +00:00
2 changed files with 58 additions and 5 deletions
+14 -2
View File
@@ -529,11 +529,23 @@ async function saveStateOnce(beforeWrite) {
}
// When the merged record names a network other than this page's, this page
// has not yet taken a switch saved elsewhere. This page's `trackedTokens`
// is then the list of the network that this page still has. So the merged
// record keeps the stored list
// is then the list of this page's network. The stored record keeps the
// list of this page's network in `networkTrackedTokens`. So a token that
// this page added or removed since its last save is added to or removed
// from that stored list. The merged record keeps the stored `trackedTokens`
// (https://git.eeqj.de/sneak/AutistMask/issues/701).
if (merged.networkId !== current.networkId) {
merged.trackedTokens = fresh.trackedTokens;
merged.networkTrackedTokens = {
...fresh.networkTrackedTokens,
[current.networkId]: mergeListByIdentity(
baseline.trackedTokens,
current.trackedTokens,
fresh.networkTrackedTokens[current.networkId],
(token) => token.address.toLowerCase(),
(base, ours) => ours,
),
};
}
merged.hasWallet = Boolean(merged.wallets && merged.wallets.length > 0);
// Stamped on every write, never merged or diffed: the record that goes to
+44 -3
View File
@@ -270,8 +270,9 @@ test("a token tracked on Sepolia when another page switched there is not tracked
// Another popup page saves a switch to Sepolia, where TST is tracked. Before
// taking that switch, this page adds a token on Ethereum Mainnet. The add's
// save does not write this page's Ethereum Mainnet list as Sepolia's list.
test("a token added before this page takes another page's switch leaves Sepolia's tokens tracked", async () => {
// save adds the token to Ethereum Mainnet's list and leaves Sepolia's list as
// it was.
test("a token added before this page takes another page's switch is tracked on Ethereum Mainnet", async () => {
const boot = await bootOnAddToken("mainnet", {
trackedTokens: [MAINNET_TOKEN],
networkTrackedTokens: { sepolia: [SEPOLIA_TOKEN] },
@@ -299,7 +300,47 @@ test("a token added before this page takes another page's switch leaves Sepolia'
const stored = boot.storage.read("autistmask");
expect(stored.networkId).toBe("sepolia");
expect(stored.trackedTokens).toEqual([SEPOLIA_TOKEN]);
expect(stored.networkTrackedTokens.mainnet).toEqual([MAINNET_TOKEN]);
expect(stored.networkTrackedTokens.mainnet).toEqual([
MAINNET_TOKEN,
{
address: NEW_TOKEN_ADDRESS,
symbol: "NEW",
decimals: 18,
name: "New Token",
},
]);
});
// Another popup page saves a switch to Sepolia, where TST is tracked. Before
// taking that switch, this page removes DAI on Ethereum Mainnet, as the `[x]`
// on a tracked token in Settings does. The removal's save drops DAI from
// Ethereum Mainnet's list and leaves Sepolia's list as it was.
test("a token removed before this page takes another page's switch is gone from Ethereum Mainnet", async () => {
const boot = await bootPopup(
unversionedValidProfile({
trackedTokens: [MAINNET_TOKEN],
networkTrackedTokens: { sepolia: [SEPOLIA_TOKEN] },
}),
);
expect(boot.pageErrors).toEqual([]);
boot.storage.write("autistmask", {
...boot.storage.read("autistmask"),
networkId: "sepolia",
trackedTokens: [SEPOLIA_TOKEN],
networkTrackedTokens: {
mainnet: [MAINNET_TOKEN],
sepolia: [SEPOLIA_TOKEN],
},
});
const { state, saveState } = require("../src/shared/state");
state.trackedTokens.splice(0, 1);
await saveState();
const stored = boot.storage.read("autistmask");
expect(stored.networkId).toBe("sepolia");
expect(stored.trackedTokens).toEqual([SEPOLIA_TOKEN]);
expect(stored.networkTrackedTokens.mainnet).toEqual([]);
});
// Another popup page tracks DAI on Ethereum Mainnet and then saves a switch to