Compare commits

..

2 Commits

Author SHA1 Message Date
3d87f8bda5 test: cover the address-poisoning filters in transactions.js (closes #160)
All checks were successful
check / check (push) Successful in 20s
src/shared/transactions.js had zero test coverage despite owning the four
anti-poisoning filters that README.md:730-814 documents as a core security
property: known token symbol verification, the 1,000-holder rule, the fraud
contract blocklist, and the dust threshold. A regression in any of them does
not crash, it just silently stops filtering, so the behaviour needs pinning
down in both directions.

Adds tests/transactions.test.js with fixtures built from the two real attacks
cited in the README: the fake "Ethereum"/"ETH" token at 0xD05339f9 with zero
holders, and the 1 gwei native dust transfer 0x2708ebdd from the look-alike
sender 0xC3c6B3b4. Every filter is proven to work when on and to be bypassed
when off, both thresholds are asserted at, just below, and just above their
boundary, and legitimate traffic (a plain ETH transfer and genuine USDC and
WETH transfers) is proven to survive all four rules.

Also covers the per-address merge and dedup that fetchRecentTransactions owns,
with debugFetch mocked at the module boundary. No test performs a network
request: global.fetch is a throwing stub and is asserted never to have been
called.

Several tests are named as documenting current behaviour where it diverges
from the README; no source file is modified here.
2026-08-09 05:05:15 +00:00
23aeae4841 feat: add About well to settings with build info and debug easter egg (#145)
All checks were successful
check / check (push) Successful in 22s
## Summary

Add a new well at the bottom of the settings view displaying application info (license, author, version, build date, git commit hash linked to Gitea), and an easter egg that reveals a debug mode toggle after clicking the version number 10 times.

## Changes

- **`src/popup/index.html`**: Added About well with license, author, version, build date, and linked commit hash. Added hidden debug well with debug mode toggle.
- **`src/popup/views/settings.js`**: Populate About well from build-time constants. Implement version click counter (10 clicks reveals debug well). Wire up debug mode toggle to state and runtime logger.
- **`src/shared/buildInfo.js`** (new): Module exporting build-time constants (`BUILD_VERSION`, `BUILD_LICENSE`, `BUILD_AUTHOR`, `BUILD_COMMIT`, `BUILD_DATE`, `GITEA_COMMIT_URL`) injected by esbuild define.
- **`build.js`**: Read git commit hash (short + full) and version from package.json, pass as esbuild `define` constants. Also reads `GIT_COMMIT_SHORT`/`GIT_COMMIT_FULL` env vars for Docker builds.
- **`Dockerfile`**: Accept `GIT_COMMIT_SHORT` and `GIT_COMMIT_FULL` build args, set as env vars for the build step.
- **`Makefile`**: Pass git commit hashes as Docker build args.
- **`src/shared/state.js`**: Add `debugMode` boolean to state (persisted).
- **`src/shared/log.js`**: Add runtime debug flag that supplements the compile-time `DEBUG` constant. Debug mode toggle updates this flag immediately.

closes #144

Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Co-authored-by: clawbot <clawbot@sneak.cloud>
Co-authored-by: user <user@Mac.lan guest wan>
Co-authored-by: clawbot <clawbot@eeqj.de>
Co-authored-by: sneak <sneak@sneak.berlin>
Reviewed-on: #145
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-07-26 18:39:09 +02:00
15 changed files with 1301 additions and 2338 deletions

View File

@@ -1,4 +1,3 @@
.git
node_modules node_modules
.DS_Store .DS_Store
dist dist

View File

@@ -23,6 +23,8 @@ get review, merge to main.
# Completed Steps # Completed Steps
- 2026-08-09: Test coverage for the address-poisoning defense in
`src/shared/transactions.js` (#160)
- 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, Makefile - 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, Makefile
shims, README Entrypoints section shims, README Entrypoints section
- 2026-03-01: About well in settings with build info and easter egg (in flight - 2026-03-01: About well in settings with build info and easter egg (in flight

View File

@@ -11,9 +11,51 @@ function ensureDir(dir) {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
} }
function getBuildInfo() {
const pkg = JSON.parse(
fs.readFileSync(path.join(__dirname, "package.json"), "utf8"),
);
let commitHash = "unknown";
try {
commitHash = execSync("git rev-parse --short HEAD", {
encoding: "utf8",
}).trim();
} catch (_) {
// not a git repo or git not available
}
let commitHashFull = "unknown";
try {
commitHashFull = execSync("git rev-parse HEAD", {
encoding: "utf8",
}).trim();
} catch (_) {
// not a git repo or git not available
}
return {
version: pkg.version,
license: pkg.license,
author: pkg.author,
commitHash,
commitHashFull,
buildDate: new Date().toISOString().slice(0, 10),
};
}
async function build() { async function build() {
console.log("Building AutistMask extension..."); console.log("Building AutistMask extension...");
const buildInfo = getBuildInfo();
console.log("Build info:", buildInfo);
const define = {
__BUILD_VERSION__: JSON.stringify(buildInfo.version),
__BUILD_LICENSE__: JSON.stringify(buildInfo.license),
__BUILD_AUTHOR__: JSON.stringify(buildInfo.author),
__BUILD_COMMIT__: JSON.stringify(buildInfo.commitHash),
__BUILD_COMMIT_FULL__: JSON.stringify(buildInfo.commitHashFull),
__BUILD_DATE__: JSON.stringify(buildInfo.buildDate),
};
// compile tailwind CSS // compile tailwind CSS
console.log("Compiling Tailwind CSS..."); console.log("Compiling Tailwind CSS...");
const tailwindInput = path.join(SRC, "popup", "styles", "main.css"); const tailwindInput = path.join(SRC, "popup", "styles", "main.css");
@@ -38,6 +80,7 @@ async function build() {
platform: "browser", platform: "browser",
target: ["chrome110", "firefox110"], target: ["chrome110", "firefox110"],
minify: true, minify: true,
define,
}); });
// bundle background script // bundle background script
@@ -49,6 +92,7 @@ async function build() {
platform: "browser", platform: "browser",
target: ["chrome110", "firefox110"], target: ["chrome110", "firefox110"],
minify: true, minify: true,
define,
}); });
// bundle content script // bundle content script
@@ -60,6 +104,7 @@ async function build() {
platform: "browser", platform: "browser",
target: ["chrome110", "firefox110"], target: ["chrome110", "firefox110"],
minify: true, minify: true,
define,
}); });
// bundle inpage script (injected into page context, separate file) // bundle inpage script (injected into page context, separate file)
@@ -71,6 +116,7 @@ async function build() {
platform: "browser", platform: "browser",
target: ["chrome110", "firefox110"], target: ["chrome110", "firefox110"],
minify: true, minify: true,
define,
}); });
// copy popup HTML // copy popup HTML

View File

@@ -1,161 +0,0 @@
#!/usr/bin/env node
// Apply enrichment data to tokenList.js: add name + url fields to each token.
import { readFileSync, writeFileSync } from "fs";
const enrichment = JSON.parse(
readFileSync("scripts/token-enrichment.json", "utf8"),
);
// Well-known URLs for major tokens not in ethereum-lists.
// These are official project websites verified from public sources.
const EXTRA_URLS = {
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": "https://weth.io",
"0xdAC17F958D2ee523a2206206994597C13D831ec7": "https://tether.to",
"0xB8c77482e45F1F44dE1745F52C74426C631bDD52": "https://www.bnbchain.org",
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": "https://www.circle.com/usdc",
"0xdC035D45d973E3EC169d2276DDab16f1e407384F": "https://sky.money",
"0x2AF5D2aD76741191D15Dfe7bF6aC92d4Bd912Ca3": "https://www.bitfinex.com",
"0x514910771AF9Ca656af840dff83E8264EcF986CA": "https://chain.link",
"0x4c9EDD5852cd905f086C759E8383e09bff1E68B3": "https://ethena.fi",
"0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d":
"https://www.worldlibertyfinancial.com",
"0x6B175474E89094C44Da98b954EedeAC495271d0F": "https://makerdao.com",
"0x6c3ea9036406852006290770BEdFcAbA0e23A0e8":
"https://www.paypal.com/us/digital-wallet/manage-money/crypto/pyusd",
"0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE": "https://shibatoken.com",
"0xdA5e1988097297dCdc1f90D4dFE7909e847CBeF6":
"https://www.worldlibertyfinancial.com",
"0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b": "https://crypto.com",
"0x582d872A1B094FC48F5DE31D3B73F2D9bE47def1": "https://ton.org",
"0x68749665FF8D2d112Fa859AA293F07A622782F38": "https://gold.tether.to",
"0x196C20DA81Fbc324EcdF55501e95Ce9f0bD84d14": "https://polkadot.network",
"0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984": "https://uniswap.org",
"0x45804880De22913dAFE09f4980848ECE6EcbAf78":
"https://www.paxos.com/paxgold",
"0x3c3a81e81dc49A522A592e7622A7E711c06bf354": "https://www.mantle.xyz",
"0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9": "https://aave.com",
"0x75231F58b43240C9718Dd58B4967c5114342a86c": "https://www.okx.com",
"0x6982508145454Ce325dDbE47a25d4ec3d2311933": "https://www.pepe.vip",
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599": "https://wbtc.network",
"0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84": "https://lido.fi",
"0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32": "https://lido.fi",
"0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72": "https://ens.domains",
"0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0": "https://polygon.technology",
"0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2": "https://makerdao.com",
"0xD533a949740bb3306d119CC777fa900bA034cd52": "https://curve.fi",
"0x111111111117dC0aa78b770fA6A738034120C302": "https://1inch.io",
"0x163f8C2467924be0ae7B5347228CABF260318753": "https://worldcoin.org",
"0xba100000625a3754423978a60c9317c58a424e3D": "https://balancer.fi",
"0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F": "https://synthetix.io",
"0x1a7e4e63778B4f12a199C062f3eFdD288afCBce8": "https://www.euler.finance",
"0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD": "https://loopring.org",
"0xC669928185DbCE49d2230CC9B0979BE6DC797957": "https://bitstable.finance",
"0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0": "https://lido.fi",
"0xBe9895146f7AF43049ca1c1AE358B0541Ea49704":
"https://www.coinbase.com/cbeth",
"0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3": "https://ondo.finance",
"0x0C10bF8FcB7Bf5412187A595ab97a3609160b5c6": "https://decentraland.org",
"0x6De037ef9aD2725EB40118Bb1702EBb27e4Aeb24": "https://www.render.com",
"0x4a220E6096B25EADb88358cb44068A3248254675": "https://www.quant.network",
"0xba5BDe662c17e2aDFF1075610382B9B691296350": "https://superrare.com",
"0x15D4c048F83bd7e37d49eA4C83a07267Ec4203dA": "https://galxe.com",
"0xc00e94Cb662C3520282E6f5717214004A7f26888": "https://compound.finance",
"0xE41d2489571d322189246DaFA5ebDe1F4699F498": "https://0x.org",
"0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e": "https://yearn.fi",
"0x6fB3e0A217407EFFf7Ca062D46c26E5d60a14d69": "https://iotex.io",
"0xD31a59c85aE9D8edEFec411D448f90841571b89c": "https://solana.com",
"0xa1faa113cbE53436Df28FF0aEe54275c13B40975": "https://alphafinance.io",
"0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF": "https://immutable.com",
"0x0f5D2fB29fb7d3CFeE444a200298f468908cC942": "https://decentraland.org",
"0x3845badAde8e6dFF049820680d1F14bD3903a5d0": "https://www.sandbox.game",
"0x4d224452801ACEd8B2F0aebE155379bb5D594381": "https://apecoin.com",
"0x6810e776880C02933D47DB1b9fc05908e5386b96": "https://gnosis.io",
"0x5f98805A4E8be255a32880FDeC7F6728C6568bA0": "https://www.liquity.org",
"0xd26114cd6EE289AccF82350c8d8487fedB8A0C07": "https://omg.network",
"0x0D8775F648430679A709E98d2b0Cb6250d2887EF":
"https://basicattentiontoken.org",
"0xc944E90C64B2c07662A292be6244BDf05Cda44a7": "https://thegraph.com",
"0x1985365e9f78359a9B6AD760e32412f4a445E862": "https://augur.net",
"0x4E15361FD6b4BB609Fa63C81A2be19d873717870": "https://fantom.foundation",
"0x853d955aCEf822Db058eb8505911ED77F175b99e": "https://frax.finance",
"0x0000000000085d4780B73119b644AE5ecd22b376": "https://www.trueusd.com",
"0x0cEC1A9154Ff802e7934Fc916Ed7Ca50bDE6844e": "https://polymesh.network",
"0x956F47F50A910163D8BF957Cf5846D573E7f87CA": "https://fei.money",
"0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0": "https://frax.finance",
"0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E": "https://curve.fi",
"0x0100546F2cD4C9D97f798fFC9755E47865FF7Ee6": "https://alchemix.fi",
"0x808507121B80c02388fAd14726482e061B8da827": "https://pendle.finance",
"0xCa14007Eff0dB1f8135f4C25B34De49AB0d42766": "https://starknet.io",
"0x8457CA5040ad67fdebbCC8EdCE889A335Bc0fbFB": "https://alt.layer",
"0x4C19596f5aAfF459fA38B0f7eD92F11AE6543784": "https://www.truflation.com",
"0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1": "https://arbitrum.io",
"0x912CE59144191C1204E64559FE8253a0e49E6548": "https://arbitrum.io",
"0x6985884C4392D348587B19cb9eAAf157F13271cd": "https://layerzero.network",
"0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB": "https://ethfi.org",
"0x18084fbA666a33d37592fA2633fD49a74DD93a88": "https://tbtc.network",
"0x9D39A5DE30e57443BfF2A8307A4256c8797A3497": "https://suilend.fi",
"0x5283D291DBCF85356A21bA090E6db59121208b44": "https://blur.io",
"0x43Dfc4159D86F3A37A5A4B3D4580b888ad7d4DDd": "https://dydx.exchange",
"0xCf0C122c6b73ff809C693DB761e7BaeBe62b6a2E": "https://floki.com",
"0xe53EC727dbDEB9E2d5456c3be40cFF031AB40A55": "https://superfarm.com",
"0x32353A6C91143bfd6C7d363B546e62a9A2489A20": "https://www.agora.finance",
"0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c": "https://www.euro-coin.com",
"0x1a03eF3d1a80b5f214e468ACD97F55714E049393": "https://gemspad.io",
"0x7122985656e38BDC0302Db86685bb972b145bD3C": "https://stone.fi",
"0xa35923162C49cF95e6BF26623385eb431ad920D3": "https://www.turbo-eth.com",
"0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF": "https://alchemix.fi",
"0x08c32b0726C5684024ea6e141C50aDe9690bBdcc": "https://stratis.io",
"0x65Ef703f5594D2573eb71Aaf55BC0CB548492df4": "https://multichain.org",
"0x1151CB3d861920e07a38e03eEAd12C32178567F6": "https://bonk.com",
"0x090185f2135308BaD17527004364eBcC2D37e5F6": "https://spell.club",
"0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6": "https://stargate.finance",
};
// Apply extra URLs where enrichment has none
for (const [addr, url] of Object.entries(EXTRA_URLS)) {
const e = enrichment[addr];
if (e && !e.url) {
e.url = url;
}
}
// Read original tokenList.js
const original = readFileSync("src/shared/tokenList.js", "utf8");
// Replace each token block: add name and url after decimals
let result = original;
let replacements = 0;
for (const [addr, data] of Object.entries(enrichment)) {
// Escape special regex characters in the address
const escaped = addr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// Match the token block: { address: "...", symbol: "...", decimals: N, }
// The closing }, may have trailing whitespace
const blockRe = new RegExp(
`(\\{\\s*address:\\s*"${escaped}",\\s*symbol:\\s*"[^"]+",\\s*decimals:\\s*\\d+),?(\\s*\\})`,
);
const match = result.match(blockRe);
if (!match) {
console.error(`WARNING: Could not find block for ${addr}`);
continue;
}
// Build replacement: original fields + name + url
const nameEscaped = data.name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
const urlEscaped = data.url.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
const replacement =
match[1] +
`,\n name: "${nameEscaped}",` +
`\n url: "${urlEscaped}",` +
match[2];
result = result.replace(blockRe, replacement);
replacements++;
}
console.error(`Applied ${replacements} enrichments to tokenList.js`);
writeFileSync("src/shared/tokenList.js", result);
console.error("Done!");

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env node
// Temporary script: fetch name + homepage URL for every token in tokenList.js
// Phase 1: CoinGecko /coins/list (1 bulk call) for names — gets 100%
// Phase 2: ethereum-lists GitHub repo for project URLs — gets ~15%
import { readFileSync, writeFileSync } from "fs";
const src = readFileSync("src/shared/tokenList.js", "utf8");
const tokens = [];
const re =
/\{\s*address:\s*"(0x[a-fA-F0-9]{40})",\s*symbol:\s*"([^"]+)",\s*decimals:\s*(\d+)/g;
let m;
while ((m = re.exec(src)) !== null) {
tokens.push({ address: m[1], symbol: m[2], decimals: parseInt(m[3]) });
}
console.error(`Parsed ${tokens.length} tokens`);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// --- Phase 1: bulk names from CoinGecko ---
console.error("Fetching CoinGecko coins list...");
const listResp = await fetch(
"https://api.coingecko.com/api/v3/coins/list?include_platform=true",
);
if (!listResp.ok) throw new Error(`CoinGecko list HTTP ${listResp.status}`);
const coins = await listResp.json();
const addrMap = new Map();
for (const coin of coins) {
const eth = (coin.platforms?.ethereum || "").toLowerCase().trim();
if (eth && eth.startsWith("0x")) {
addrMap.set(eth, { name: coin.name, id: coin.id });
}
}
console.error(`CoinGecko map: ${addrMap.size} Ethereum tokens`);
const enriched = new Map();
for (const t of tokens) {
const info = addrMap.get(t.address.toLowerCase());
enriched.set(t.address, { name: info?.name || t.symbol, url: "" });
}
console.error(
`Matched ${[...enriched.values()].filter((e) => e.name).length}/${tokens.length} names`,
);
// --- Phase 2: ethereum-lists GitHub for URLs ---
console.error("Fetching URLs from ethereum-lists GitHub...");
const CONCURRENT = 10;
let urlsFound = 0;
for (let i = 0; i < tokens.length; i += CONCURRENT) {
const batch = tokens.slice(i, i + CONCURRENT);
await Promise.allSettled(
batch.map(async (t) => {
try {
const r = await fetch(
`https://raw.githubusercontent.com/ethereum-lists/tokens/master/tokens/eth/${t.address}.json`,
);
if (!r.ok) return;
const data = await r.json();
const website = (data.website || "").trim();
if (website && website !== "https://") {
enriched.get(t.address).url = website;
urlsFound++;
}
} catch {
// ignore
}
}),
);
if (i % 100 === 0) {
console.error(
` GitHub: ${Math.min(i + CONCURRENT, tokens.length)}/${tokens.length}, ${urlsFound} URLs`,
);
}
await sleep(200);
}
console.error(`Phase 2 done: ${urlsFound} URLs from ethereum-lists`);
// --- Output ---
const output = {};
for (const [addr, e] of enriched) {
output[addr] = { name: e.name, url: e.url };
}
writeFileSync("scripts/token-enrichment.json", JSON.stringify(output, null, 2));
console.error(`Done! ${tokens.length} names, ${urlsFound} URLs.`);

File diff suppressed because it is too large Load Diff

View File

@@ -1002,6 +1002,64 @@
</p> </p>
<div id="settings-denied-sites"></div> <div id="settings-denied-sites"></div>
</div> </div>
<div class="bg-well p-3 mx-1 mb-3">
<h3 class="font-bold mb-1">About</h3>
<p class="text-xs mb-2">
<a
href="https://git.eeqj.de/sneak/AutistMask"
class="underline decoration-dashed"
target="_blank"
rel="noopener noreferrer"
>AutistMask</a
>
— Minimal Ethereum wallet browser extension.
</p>
<div class="text-xs">
<div class="mb-1">
<span class="text-muted">License:</span>
<span id="about-license"></span>
</div>
<div class="mb-1">
<span class="text-muted">Author:</span>
<span id="about-author"></span>
</div>
<div class="mb-1">
<span class="text-muted">Version:</span>
<span
id="about-version"
class="cursor-pointer select-none"
></span>
</div>
<div class="mb-1">
<span class="text-muted">Release date:</span>
<span id="about-release-date"></span>
</div>
<div>
<span class="text-muted">Commit:</span>
<a
id="about-commit-link"
class="underline decoration-dashed"
target="_blank"
rel="noopener noreferrer"
></a>
</div>
</div>
</div>
<div
id="settings-debug-well"
class="bg-well p-3 mx-1 mb-3"
style="display: none"
>
<h3 class="font-bold mb-1">Debug</h3>
<label
class="text-xs flex items-center gap-1 cursor-pointer"
>
<input type="checkbox" id="settings-debug-mode" />
Enable debug mode
</label>
</div>
</div> </div>
<!-- ============ DELETE WALLET CONFIRM ============ --> <!-- ============ DELETE WALLET CONFIRM ============ -->

View File

@@ -1,18 +1,14 @@
// AutistMask popup entry point. // AutistMask popup entry point.
// Loads state, initializes views, triggers first render. // Loads state, initializes views, triggers first render.
const { DEBUG } = require("../shared/constants"); const { state, saveState, loadState } = require("../shared/state");
const { const { setRuntimeDebug } = require("../shared/log");
state,
saveState,
loadState,
currentNetwork,
} = require("../shared/state");
const { refreshPrices } = require("../shared/prices"); const { refreshPrices } = require("../shared/prices");
const { refreshBalances } = require("../shared/balances"); const { refreshBalances } = require("../shared/balances");
const { const {
$, $,
showView, showView,
updateDebugBanner,
setRenderMain, setRenderMain,
pushCurrentView, pushCurrentView,
goBack, goBack,
@@ -209,21 +205,11 @@ async function init() {
await loadState(); await loadState();
applyTheme(state.theme); applyTheme(state.theme);
const net = currentNetwork(); // Sync runtime debug flag from persisted state before first render
if (DEBUG || net.isTestnet) { setRuntimeDebug(state.debugMode);
const banner = document.createElement("div");
banner.id = "debug-banner"; // Create the debug/testnet banner if needed (uses runtime debug state)
if (DEBUG && net.isTestnet) { updateDebugBanner();
banner.textContent = "DEBUG / INSECURE [TESTNET]";
} else if (net.isTestnet) {
banner.textContent = "[TESTNET]";
} else {
banner.textContent = "DEBUG / INSECURE";
}
banner.style.cssText =
"background:#c00;color:#fff;text-align:center;font-size:10px;padding:1px 0;font-family:monospace;position:sticky;top:0;z-index:9999;";
document.body.prepend(banner);
}
// Auto-default active address // Auto-default active address
if ( if (

View File

@@ -10,7 +10,7 @@
--color-border: #000000; --color-border: #000000;
--color-border-light: #cccccc; --color-border-light: #cccccc;
--color-hover: #eeeeee; --color-hover: #eeeeee;
--color-well: #f5f5f5; --color-well: #e8e8e8;
--color-danger-well: #fef2f2; --color-danger-well: #fef2f2;
--color-section: #dddddd; --color-section: #dddddd;
} }

View File

@@ -1,6 +1,6 @@
// Shared DOM helpers used by all views. // Shared DOM helpers used by all views.
const { DEBUG } = require("../../shared/constants"); const { isDebug } = require("../../shared/log");
const { const {
formatUsd, formatUsd,
getPrice, getPrice,
@@ -59,19 +59,37 @@ function showView(name) {
clearFlash(); clearFlash();
state.currentView = name; state.currentView = name;
saveState(); saveState();
updateDebugBanner(name);
}
// Create or update the debug/insecure warning banner.
// Called on every view switch and after the settings debug toggle changes.
// The banner is shown when the compile-time DEBUG constant is true OR when
// the user has enabled runtime debug mode via the settings easter egg, OR
// when the active network is a testnet.
function updateDebugBanner(viewName) {
const debug = isDebug();
const net = currentNetwork(); const net = currentNetwork();
if (DEBUG || net.isTestnet) { const show = debug || net.isTestnet;
const banner = document.getElementById("debug-banner"); let banner = document.getElementById("debug-banner");
if (banner) { if (show) {
if (DEBUG && net.isTestnet) { if (!banner) {
banner.textContent = banner = document.createElement("div");
"DEBUG / INSECURE [TESTNET] (" + name + ")"; banner.id = "debug-banner";
} else if (net.isTestnet) { banner.style.cssText =
banner.textContent = "[TESTNET]"; "background:#c00;color:#fff;text-align:center;font-size:10px;padding:1px 0;font-family:monospace;position:sticky;top:0;z-index:9999;";
} else { document.body.prepend(banner);
banner.textContent = "DEBUG / INSECURE (" + name + ")";
}
} }
const suffix = viewName ? " (" + viewName + ")" : "";
if (debug && net.isTestnet) {
banner.textContent = "DEBUG / INSECURE [TESTNET]" + suffix;
} else if (net.isTestnet) {
banner.textContent = "[TESTNET]" + suffix;
} else {
banner.textContent = "DEBUG / INSECURE" + suffix;
}
} else if (banner) {
banner.remove();
} }
} }
@@ -417,6 +435,7 @@ module.exports = {
showError, showError,
hideError, hideError,
showView, showView,
updateDebugBanner,
setRenderMain, setRenderMain,
pushCurrentView, pushCurrentView,
goBack, goBack,

View File

@@ -1,8 +1,10 @@
const { const {
$, $,
showView, showView,
updateDebugBanner,
showFlash, showFlash,
escapeHtml, escapeHtml,
flashCopyFeedback,
goBack, goBack,
pushCurrentView, pushCurrentView,
} = require("./helpers"); } = require("./helpers");
@@ -10,12 +12,23 @@ const { applyTheme } = require("../theme");
const { state, saveState, currentNetwork } = require("../../shared/state"); const { state, saveState, currentNetwork } = require("../../shared/state");
const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks"); const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks");
const { onChainSwitch } = require("../../shared/chainSwitch"); const { onChainSwitch } = require("../../shared/chainSwitch");
const { log, debugFetch } = require("../../shared/log"); const { log, debugFetch, setRuntimeDebug } = require("../../shared/log");
const deleteWallet = require("./deleteWallet"); const deleteWallet = require("./deleteWallet");
const {
BUILD_VERSION,
BUILD_LICENSE,
BUILD_AUTHOR,
BUILD_COMMIT,
BUILD_DATE,
GITEA_COMMIT_URL,
} = require("../../shared/buildInfo");
const runtime = const runtime =
typeof browser !== "undefined" ? browser.runtime : chrome.runtime; typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
let versionClickCount = 0;
let versionClickTimer = null;
function renderSiteList(containerId, siteMap, stateKey) { function renderSiteList(containerId, siteMap, stateKey) {
const container = $(containerId); const container = $(containerId);
const hostnames = [...new Set(Object.values(siteMap).flat())]; const hostnames = [...new Set(Object.values(siteMap).flat())];
@@ -142,6 +155,28 @@ function show() {
renderSiteLists(); renderSiteLists();
renderWalletListSettings(); renderWalletListSettings();
// Populate About well
$("about-license").textContent = BUILD_LICENSE;
// Show only the name part of the author field (strip email)
const authorName = BUILD_AUTHOR.replace(/\s*<[^>]+>/, "");
$("about-author").textContent = authorName;
$("about-version").textContent = BUILD_VERSION;
$("about-release-date").textContent = BUILD_DATE;
$("about-commit-link").textContent = BUILD_COMMIT;
$("about-commit-link").href = GITEA_COMMIT_URL;
// Reset version click counter each time settings opens
versionClickCount = 0;
// Show debug well if debug mode is already enabled
const debugWell = $("settings-debug-well");
if (state.debugMode) {
debugWell.style.display = "";
} else {
debugWell.style.display = "none";
}
$("settings-debug-mode").checked = state.debugMode;
showView("settings"); showView("settings");
} }
@@ -289,6 +324,66 @@ function init(ctx) {
ctx.showSettingsAddTokenView, ctx.showSettingsAddTokenView,
); );
// Bright saturated colors for easter egg flashes (clicks 610)
const easterEggColors = [
"#ff0055", // hot pink
"#00cc44", // vivid green
"#3366ff", // electric blue
"#ff9900", // bright orange
"#aa00ff", // vivid purple
];
// Easter egg: click version 10 times to reveal the debug well.
// Each click does a copy-flash animation. After 5 clicks, each
// additional click flashes a different bright saturated color.
$("about-version").addEventListener("click", () => {
versionClickCount++;
clearTimeout(versionClickTimer);
// Reset counter if user stops clicking for 3 seconds
versionClickTimer = setTimeout(() => {
versionClickCount = 0;
}, 3000);
const el = $("about-version");
if (versionClickCount > 5) {
// Colored flash for clicks 610
const colorIdx = versionClickCount - 6;
const color = easterEggColors[colorIdx % easterEggColors.length];
el.classList.remove("copy-flash-fade");
el.style.backgroundColor = color;
el.style.color = "#ffffff";
setTimeout(() => {
el.style.backgroundColor = "";
el.style.color = "";
el.classList.add("copy-flash-fade");
setTimeout(() => {
el.classList.remove("copy-flash-fade");
}, 275);
}, 75);
} else {
// Standard copy-flash for clicks 15
flashCopyFeedback(el);
}
if (versionClickCount >= 10) {
versionClickCount = 0;
clearTimeout(versionClickTimer);
$("settings-debug-well").style.display = "";
}
});
// Debug mode toggle — update runtime flag, persist, and re-render banner
$("settings-debug-mode").addEventListener("change", async () => {
state.debugMode = $("settings-debug-mode").checked;
setRuntimeDebug(state.debugMode);
await saveState();
updateDebugBanner(state.currentView);
});
// Sync runtime debug flag on init
setRuntimeDebug(state.debugMode);
$("btn-settings-back").addEventListener("click", () => { $("btn-settings-back").addEventListener("click", () => {
goBack(); goBack();
}); });

35
src/shared/buildInfo.js Normal file
View File

@@ -0,0 +1,35 @@
// Build-time constants injected by esbuild define in build.js.
// These globals are replaced at bundle time with string literals.
/* global __BUILD_VERSION__, __BUILD_LICENSE__, __BUILD_AUTHOR__,
__BUILD_COMMIT__, __BUILD_COMMIT_FULL__, __BUILD_DATE__ */
const BUILD_VERSION =
typeof __BUILD_VERSION__ !== "undefined" ? __BUILD_VERSION__ : "dev";
const BUILD_LICENSE =
typeof __BUILD_LICENSE__ !== "undefined" ? __BUILD_LICENSE__ : "GPL-3.0";
const BUILD_AUTHOR =
typeof __BUILD_AUTHOR__ !== "undefined"
? __BUILD_AUTHOR__
: "sneak <sneak@sneak.berlin>";
const BUILD_COMMIT =
typeof __BUILD_COMMIT__ !== "undefined" ? __BUILD_COMMIT__ : "unknown";
const BUILD_COMMIT_FULL =
typeof __BUILD_COMMIT_FULL__ !== "undefined"
? __BUILD_COMMIT_FULL__
: "unknown";
const BUILD_DATE =
typeof __BUILD_DATE__ !== "undefined" ? __BUILD_DATE__ : "unknown";
const GITEA_COMMIT_URL =
"https://git.eeqj.de/sneak/AutistMask/commit/" + BUILD_COMMIT_FULL;
module.exports = {
BUILD_VERSION,
BUILD_LICENSE,
BUILD_AUTHOR,
BUILD_COMMIT,
BUILD_COMMIT_FULL,
BUILD_DATE,
GITEA_COMMIT_URL,
};

View File

@@ -1,12 +1,27 @@
// Leveled logger. Outputs to console with [AutistMask] prefix. // Leveled logger. Outputs to console with [AutistMask] prefix.
// Level is DEBUG when the DEBUG constant is true, INFO otherwise. // Level is DEBUG when the compile-time DEBUG constant is true or the runtime
// debugMode state flag is enabled. The runtime flag is checked lazily so it
// responds immediately when toggled in settings.
const { DEBUG } = require("./constants"); const { DEBUG } = require("./constants");
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
const threshold = DEBUG ? LEVELS.debug : LEVELS.info;
// Runtime debug mode flag — set by settings.js when the user toggles debug
// mode via the easter egg. Kept here as a simple mutable reference so it can
// be updated without circular dependency issues with state.js.
let _runtimeDebug = false;
function setRuntimeDebug(enabled) {
_runtimeDebug = enabled;
}
function isDebug() {
return DEBUG || _runtimeDebug;
}
function emit(level, method, args) { function emit(level, method, args) {
const threshold = isDebug() ? LEVELS.debug : LEVELS.info;
if (LEVELS[level] >= threshold) { if (LEVELS[level] >= threshold) {
console[method]("[AutistMask]", ...args); console[method]("[AutistMask]", ...args);
} }
@@ -37,4 +52,4 @@ async function debugFetch(url, opts) {
return resp; return resp;
} }
module.exports = { log, debugFetch }; module.exports = { log, debugFetch, setRuntimeDebug, isDebug };

View File

@@ -29,6 +29,7 @@ const DEFAULT_STATE = {
fraudContracts: [], fraudContracts: [],
tokenHolderCache: {}, tokenHolderCache: {},
theme: "system", theme: "system",
debugMode: false,
}; };
const state = { const state = {
@@ -68,6 +69,7 @@ async function saveState() {
fraudContracts: state.fraudContracts, fraudContracts: state.fraudContracts,
tokenHolderCache: state.tokenHolderCache, tokenHolderCache: state.tokenHolderCache,
theme: state.theme, theme: state.theme,
debugMode: state.debugMode,
currentView: state.currentView, currentView: state.currentView,
selectedWallet: state.selectedWallet, selectedWallet: state.selectedWallet,
selectedAddress: state.selectedAddress, selectedAddress: state.selectedAddress,
@@ -128,6 +130,8 @@ async function loadState() {
state.fraudContracts = saved.fraudContracts || []; state.fraudContracts = saved.fraudContracts || [];
state.tokenHolderCache = saved.tokenHolderCache || {}; state.tokenHolderCache = saved.tokenHolderCache || {};
state.theme = saved.theme || "system"; state.theme = saved.theme || "system";
state.debugMode =
saved.debugMode !== undefined ? saved.debugMode : false;
state.currentView = saved.currentView || null; state.currentView = saved.currentView || null;
state.selectedWallet = state.selectedWallet =
saved.selectedWallet !== undefined ? saved.selectedWallet : null; saved.selectedWallet !== undefined ? saved.selectedWallet : null;

1002
tests/transactions.test.js Normal file

File diff suppressed because it is too large Load Diff