fix: only the user switches the wallet's network (closes #408)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

A connected site's wallet_switchEthereumChain request for the other
supported network now opens a prompt in its own window, through the
existing approval machinery, naming the site and both networks. The
network, endpoints, balances and caches change, and chainChanged is
sent, only when the user approves it; rejecting or closing the prompt
answers 4001. One such prompt per site at a time; a request for the
active network needs none. The approval window no longer shows the
connection prompt while it waits for the approval's description,
since both prompts answer on the same port.

Model: opus-5-5
This commit is contained in:
2026-10-08 01:45:21 +00:00
parent ca18beb97f
commit ca5b42eaae
18 changed files with 908 additions and 123 deletions
+59 -16
View File
@@ -414,16 +414,18 @@ content script, the inpage provider, the background worker and the approval
popup all have to work together. A local test page is served by the route
handler on a reserved-TLD origin, gets `window.ethereum` from the shipped
`MAIN`-world content script like any other page, and drives
`eth_requestAccounts`, `personal_sign`, `eth_signTypedData_v4` and
`eth_sendTransaction` through the real prompts. Every signature is recovered in
the runner and compared against the active address, the transaction assertions
run against the raw signed transaction captured at `eth_sendRawTransaction`
rather than against anything the extension reported, rejecting each prompt is
required to return a rejection to the page rather than hang or resolve, a prompt
raised while another approval window has focus is required to open a window of
its own, and the password is required to be absent from every message the
approval window sends to the background — with the message that would carry it
required to be present, so that check cannot pass by observing nothing. That
`eth_requestAccounts`, `personal_sign`, `eth_signTypedData_v4`,
`eth_sendTransaction` and `wallet_switchEthereumChain` through the real prompts.
Every signature is recovered in the runner and compared against the active
address, the transaction assertions run against the raw signed transaction
captured at `eth_sendRawTransaction` rather than against anything the extension
reported, rejecting each prompt is required to return a rejection to the page
rather than hang or resolve, a network switch request is required to leave the
stored network unchanged and send no `chainChanged` until it is approved, a
prompt raised while another approval window has focus is required to open a
window of its own, and the password is required to be absent from every message
the approval window sends to the background — with the message that would carry
it required to be present, so that check cannot pass by observing nothing. That
last one is the standing floor under
[#157](https://git.eeqj.de/sneak/AutistMask/issues/157).
@@ -525,10 +527,11 @@ Chrome that ever changes this fails the run instead of passing it.
`make test-e2e-firefox` builds `dist/firefox/` and drives the **real popup in a
real Firefox**, installed as an unpacked MV2 temporary add-on via geckodriver.
It covers popup load, the StateRecovery screen (the same cases as the Chrome
suite), wallet creation through the UI, the Add Token screen, and the four dApp
round trips — `eth_requestAccounts`, `personal_sign`, `eth_sendTransaction`, and
a closed approval window rejecting with EIP-1193 4001 — driven through the real
content script, background page and approval windows.
suite), wallet creation through the UI, the Add Token screen, and the dApp round
trips — `eth_requestAccounts`, `personal_sign`, `wallet_switchEthereumChain`
rejected and then approved, `eth_sendTransaction`, and a closed approval window
rejecting with EIP-1193 4001 — driven through the real content script,
background page and approval windows.
The suite lives in `tests/e2e/firefox/`. Its WebDriver client (`driver.js`) has
**no npm dependencies at all**: it is built on global `fetch` and
@@ -1783,7 +1786,9 @@ view would leave a wallet one click from deletion.
- Display: "Show tracked tokens with zero balance" checkbox, "UTC
Timestamps" checkbox, and a Theme selector (System / Light / Dark)
- Network: network selector (Ethereum Mainnet / Sepolia Testnet); switching
resets the RPC and Blockscout endpoints to that network's defaults
resets the RPC and Blockscout endpoints to that network's defaults. The
network changes only here, or when the user approves a site's request on
**NetworkApproval**
- Ethereum RPC: endpoint URL input + "Save" button (validated against
`eth_chainId` before being saved)
- Blockscout API: endpoint URL input + "Save" button (validated against
@@ -2071,7 +2076,10 @@ view would leave a wallet one click from deletion.
no window and takes no nonce, and the site can send it again once the pending
one is answered. The window is centred on the browser window the user was last
in; if that was another approval window, or the browser refuses the centred
position, the browser picks the position.
position, the browser picks the position. The network shown is the one the
transaction was populated on, and a site cannot switch it without the user:
its `wallet_switchEthereumChain` request changes the network only when the
user approves it on **NetworkApproval**.
- **Elements**:
- "Transaction Request" heading
- Phishing warning banner (shown when the hostname is on the phishing
@@ -2162,6 +2170,39 @@ view would leave a wallet one click from deletion.
- Popup window closed without answering → the request is rejected with
EIP-1193 code 4001
#### NetworkApproval (`approve-network`)
- **When**: A connected website asks to switch the network with
`wallet_switchEthereumChain`, naming a supported network other than the active
one. Only the user switches the network: until the user approves the request
here, it changes nothing — not the network, the RPC and Blockscout endpoints,
the balances or the cached token data — and no page is sent `chainChanged`.
Opened the same way as TxApproval, in a separate popup window. Only one exists
per site at a time: a further switch request from a site whose switch request
is still unanswered is refused with EIP-1193 code `-32002` and opens no
window. A request naming the network already active is answered at once and
opens nothing; one naming an unsupported chain is refused with code `4902`,
and one from a site that is not connected with code `4100`.
`wallet_addEthereumChain` never switches the network.
- **Elements**:
- "Network Switch Request" heading
- Phishing warning banner (shown when the hostname is on the phishing
blocklist)
- Site origin (bold, scheme and port included) + "wants to switch the
wallet's network."
- Current network: its name
- Requested network: its name
- A line saying that switching changes the network for the whole wallet and
for every site
- "Switch" / "Reject" buttons
- **Transitions**:
- "Switch" → closes popup; the background switches the network as
**Settings** does, sends `chainChanged` to every open tab, and answers the
site with success
- "Reject" → closes popup; the site is answered with EIP-1193 code 4001 and
nothing changes
- Popup window closed without answering → the same as "Reject"
#### StateRecovery (`state-recovery`)
- **When**: the stored profile fails `assertStateUsable()`. At open, that is
@@ -2456,6 +2497,8 @@ logged.
- Sign transactions requested by connected sites (`eth_sendTransaction`)
- Sign messages (`personal_sign`, `eth_sign`)
- Sign typed data (`eth_signTypedData_v4`, `eth_signTypedData`)
- Switch network at a connected site's request, once the user approves it
(`wallet_switchEthereumChain`)
- Human-readable transaction decoding (ERC-20, Uniswap Universal Router)
- ETH/USD and token/USD price display
- Configurable RPC endpoint and Blockscout API