fix: add a Settings toggle for known-symbol spoof verification (closes #176)
Some checks failed
check / check (push) Has been cancelled

The README promises all four token-spam filters "default to on but can be
individually disabled". Known-symbol spoof verification had no state flag, no
checkbox and no consulted setting: filterTransactions() applied it before any
filter setting was read, so three of the four documented filters were
configurable and the fourth was mandatory.

Adds hideSpoofedSymbols, default on, persisted and migrated so a profile
written before the setting existed loads it as on rather than undefined. The
flag is fail-safe in the pure function too: only an explicit false disables
the check, so a caller that omits the key keeps it.

Turning the setting off also stops the fraud-contract learning. That learning
is fed only by this check, and leaving it on would make the setting a no-op:
the contract it recorded would hide the very row the user asked to see, via
the fraud-contract rule that is on by default.

Scope: the setting governs the transaction history. The same check on the
balance list and the send-screen token selector stays unconditional — those
decide which tokens the user can act on, not what the history displays. The
README's user-configurable paragraph now states what each of the four
settings actually reaches, which is not uniform.

The two `current behaviour:` tests pinning the filter as undisableable are
inverted rather than deleted, and joined by coverage for the bypass, the
halted learning, the untouched sibling rules and the storage round-trip.
This commit is contained in:
clawbot
2026-08-11 13:05:40 +00:00
parent f455b0ae7f
commit d0202fde58
12 changed files with 224 additions and 28 deletions

View File

@@ -102,3 +102,60 @@ describe("loadState hasWallet reconciliation", () => {
expect(mod.state.activeAddress).toBe(ADDRESS);
});
});
// The known-symbol spoof filter is a safety filter, so an existing profile
// stored before the setting existed must load with it on rather than with
// undefined, which would read as off.
describe("hideSpoofedSymbols persistence", () => {
test("defaults to on with empty storage", async () => {
const { mod } = loadModuleWith(null);
await mod.loadState();
expect(mod.state.hideSpoofedSymbols).toBe(true);
});
test("a profile stored without the key loads with it on", async () => {
const { mod } = loadModuleWith({ wallets: oneWallet() });
await mod.loadState();
expect(mod.state.hideSpoofedSymbols).toBe(true);
});
test("an explicit false survives the load", async () => {
const { mod } = loadModuleWith({
wallets: oneWallet(),
hideSpoofedSymbols: false,
});
await mod.loadState();
expect(mod.state.hideSpoofedSymbols).toBe(false);
});
test("saveState persists the flag", async () => {
const { mod, set } = loadModuleWith(null);
mod.state.hideSpoofedSymbols = false;
await mod.saveState();
expect(set).toHaveBeenCalledWith({
autistmask: expect.objectContaining({ hideSpoofedSymbols: false }),
});
});
test("the flag round-trips off through save and load", async () => {
const first = loadModuleWith(null);
first.mod.state.hideSpoofedSymbols = false;
await first.mod.saveState();
const persisted = first.set.mock.calls[0][0].autistmask;
const second = loadModuleWith(persisted);
await second.mod.loadState();
expect(second.mod.state.hideSpoofedSymbols).toBe(false);
});
test("the flag round-trips back on through save and load", async () => {
const first = loadModuleWith(null);
first.mod.state.hideSpoofedSymbols = true;
await first.mod.saveState();
const persisted = first.set.mock.calls[0][0].autistmask;
const second = loadModuleWith(persisted);
await second.mod.loadState();
expect(second.mod.state.hideSpoofedSymbols).toBe(true);
});
});