2 Commits
Author SHA1 Message Date
sneak 0ad3a8a0a3 fix: Back from Settings no longer lands on a secret screen left by the gear (closes #461)
check / check (push) Successful in 8m7s
e2e / e2e-chrome (push) Successful in 5m52s
e2e / e2e-firefox (push) Successful in 3m15s
Leaving the private key export or recovery phrase screen drops the
selection it was showing, but the settings gear had just pushed the
screen onto the Back stack, so Back from Settings landed on a password
prompt that could only fail. Each screen's leave handler now also takes
it off the top of the stack, which is what a reopened popup already does
to these screens. Back from Settings goes to the address screen for the
export screen; for the recovery phrase screen, opened from Settings, it
stays on Settings once, as after a reopen.

Jest tests drive the gear and then Back, and each screen's own Back, for
both screens; leavePrivkeyScreen() in the e2e suite expects the address
screen.

Model: opus-5-5
2026-10-06 22:49:49 +00:00
clawbot cd730241bb fix: a token symbol read off a contract is never stored cut mid-emoji (closes #458)
check / check (push) Successful in 6m10s
e2e / e2e-chrome (push) Successful in 5m25s
e2e / e2e-firefox (push) Successful in 3m4s
lookupTokenInfo() cut the symbol at 12 and the name at 64 UTF-16 units,
so an emoji outside the Basic Multilingual Plane could be cut between its
two halves and the half left over stored and shown as U+FFFD. Both are
now cut on code points, as displaySymbol() does. A symbol already stored
broken is not repaired.

Model: opus-5-5
2026-10-06 23:59:21 +02:00
3 changed files with 63 additions and 3 deletions
+9
View File
@@ -54,6 +54,15 @@ but the review is broader than any of them.
`leavePrivkeyScreen()` in `tests/e2e/run.js` expects the address screen after
Settings.
- 2026-10-06: A token symbol or name read off a contract is no longer stored cut
between the two halves of an emoji
([#458](https://git.eeqj.de/sneak/AutistMask/issues/458)). `lookupTokenInfo()`
cut both by UTF-16 units; it now counts code points, as `displaySymbol()` has
since [#329](https://git.eeqj.de/sneak/AutistMask/issues/329).
`tests/tokenLookupTruncation.test.js` looks up a token whose symbol and name
are made of emoji outside the Basic Multilingual Plane. A symbol already
stored broken is not repaired.
- 2026-10-06: Reloading or closing the popup no longer logs a request it cancels
as a failure in the transaction lists and ENS name lookups on the address and
token screens, the address scan after a wallet is created, the endpoint checks
+6 -3
View File
@@ -334,9 +334,12 @@ async function lookupTokenInfo(contractAddress, rpcUrl, networkId, signal) {
name = symbol;
}
// Truncate to prevent storage of excessively long values from RPC
name = String(name).slice(0, 64);
symbol = String(symbol).slice(0, 12);
// Truncate to prevent storage of excessively long values from RPC.
// Counted in code points, as displaySymbol() counts them, so the cut never
// falls between the two halves of an emoji and stores a half that renders
// as U+FFFD.
name = Array.from(String(name)).slice(0, 64).join("");
symbol = Array.from(String(symbol)).slice(0, 12).join("");
log.infof("Token resolved:", symbol, "decimals", Number(decimals));
return { name, symbol, decimals: Number(decimals) };
+48
View File
@@ -0,0 +1,48 @@
// lookupTokenInfo() cuts the symbol and name it reads off a contract on
// code-point boundaries, as displaySymbol() counts them, so an emoji outside
// the Basic Multilingual Plane is never stored cut in half
// (https://git.eeqj.de/sneak/AutistMask/issues/458).
// An emoji outside the Basic Multilingual Plane: two UTF-16 units.
const FOX = "\u{1F98A}";
// The leading "A" puts every emoji at an odd UTF-16 offset, so a cut that
// counts UTF-16 units lands between the two halves of one.
const SYMBOL = "A" + FOX.repeat(12);
const NAME = "A" + FOX.repeat(64);
// The contract answers symbol(), decimals() and name() with the values above.
jest.doMock("ethers", () => ({
...jest.requireActual("ethers"),
Contract: function () {
return {
symbol: async () => SYMBOL,
decimals: async () => 18n,
name: async () => NAME,
};
},
}));
const { lookupTokenInfo } = require("../src/shared/balances");
const ADDRESS = "0x1111111111111111111111111111111111111111";
const RPC_URL = "https://rpc.example.invalid";
beforeEach(() => {
// A token found is logged at info level, which is not under test.
jest.spyOn(console, "log").mockImplementation(() => {});
});
afterEach(() => {
jest.restoreAllMocks();
});
test("the symbol is cut to 12 code points, never inside an emoji", async () => {
const { symbol } = await lookupTokenInfo(ADDRESS, RPC_URL, "mainnet");
expect(symbol).toBe("A" + FOX.repeat(11));
});
test("the name is cut to 64 code points, never inside an emoji", async () => {
const { name } = await lookupTokenInfo(ADDRESS, RPC_URL, "mainnet");
expect(name).toBe("A" + FOX.repeat(63));
});