fix: only the user switches the wallet's network (closes #408)
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:
@@ -1741,6 +1741,54 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ NETWORK SWITCH APPROVAL ============ -->
|
||||
<div id="view-approve-network" class="view hidden">
|
||||
<h2 class="font-bold mb-2">Network Switch Request</h2>
|
||||
<div
|
||||
id="approve-network-phishing-warning"
|
||||
class="mb-3 p-2 text-xs font-bold hidden bg-red-100 text-red-800 border-2 border-red-600 rounded-md"
|
||||
>
|
||||
⚠️ PHISHING WARNING: This site is on a known phishing
|
||||
blocklist. Proceed with extreme caution.
|
||||
</div>
|
||||
<p class="mb-2">
|
||||
<span id="approve-network-origin" class="font-bold"></span>
|
||||
wants to switch the wallet's network.
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<div class="text-xs text-muted mb-1">Current network</div>
|
||||
<div
|
||||
id="approve-network-current"
|
||||
class="text-xs font-bold"
|
||||
></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-xs text-muted mb-1">Requested network</div>
|
||||
<div
|
||||
id="approve-network-requested"
|
||||
class="text-xs font-bold"
|
||||
></div>
|
||||
</div>
|
||||
<p class="mb-3 text-xs">
|
||||
Switching changes the network for the whole wallet and for
|
||||
every site, not only for this one.
|
||||
</p>
|
||||
<div class="flex justify-between">
|
||||
<button
|
||||
id="btn-approve-network"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Switch
|
||||
</button>
|
||||
<button
|
||||
id="btn-reject-network"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ STATE RECOVERY ============ -->
|
||||
<!--
|
||||
Shown when the stored profile cannot be read at all. Every
|
||||
|
||||
+2
-2
@@ -238,9 +238,9 @@ async function init() {
|
||||
// rejection rather than an uncaught error — measured as still failing
|
||||
// the run on both harnesses (Playwright `pageerror`, and the Firefox
|
||||
// driver's console-service drain), so nothing is lost by leaving it
|
||||
// on that path.
|
||||
// on that path. show() puts the approval's own screen up once the
|
||||
// background has described it.
|
||||
approval.show(approvalId);
|
||||
showView("approve-site");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+46
-11
@@ -14,6 +14,7 @@ const {
|
||||
} = require("./helpers");
|
||||
const { state, saveState } = require("../../shared/state");
|
||||
const {
|
||||
networkById,
|
||||
networkByChainId,
|
||||
nativeCurrencyByChainId,
|
||||
} = require("../../shared/networks");
|
||||
@@ -752,9 +753,29 @@ function showSignApproval(details) {
|
||||
);
|
||||
}
|
||||
|
||||
function showNetworkApproval(details) {
|
||||
showPhishingWarning(
|
||||
"approve-network-phishing-warning",
|
||||
details.isPhishingDomain,
|
||||
);
|
||||
$("approve-network-origin").textContent = details.origin;
|
||||
$("approve-network-current").textContent = networkById(
|
||||
details.currentNetworkId,
|
||||
).name;
|
||||
$("approve-network-requested").textContent = networkById(
|
||||
details.requestedNetworkId,
|
||||
).name;
|
||||
showView("approve-network");
|
||||
}
|
||||
|
||||
// Awaited by nobody: the popup entry point calls this and moves on. It
|
||||
// therefore has to absorb its own failure, and a background that cannot
|
||||
// describe the approval is the same outcome as an approval that is gone.
|
||||
//
|
||||
// Nothing is on screen until the background has described the approval, so
|
||||
// the screen shown is always the one for the approval this window answers:
|
||||
// the connection prompt's "Allow" and the network switch prompt's "Switch"
|
||||
// answer on the same port, and either would approve the other.
|
||||
async function show(id) {
|
||||
approvalId = id;
|
||||
approvalPort = runtimeApi().connect({ name: "approval:" + id });
|
||||
@@ -778,6 +799,10 @@ async function show(id) {
|
||||
showSignApproval(details);
|
||||
return;
|
||||
}
|
||||
if (details.type === "network") {
|
||||
showNetworkApproval(details);
|
||||
return;
|
||||
}
|
||||
// Site connection approval
|
||||
showPhishingWarning(
|
||||
"approve-site-phishing-warning",
|
||||
@@ -787,6 +812,7 @@ async function show(id) {
|
||||
$("approve-address").innerHTML = approvalAddressHtml(state.activeAddress);
|
||||
attachCopyHandlers("view-approve-site");
|
||||
$("approve-remember").checked = state.rememberSiteChoice;
|
||||
showView("approve-site");
|
||||
}
|
||||
|
||||
let approvalId = null;
|
||||
@@ -866,20 +892,21 @@ function clearSignPassword() {
|
||||
hideError("approve-sign-error");
|
||||
}
|
||||
|
||||
// Answer a site-connection approval and close. The decision goes out on the
|
||||
// approval port — see approvalPort above for why — and carries no approval id,
|
||||
// because the port name already names the approval the background will settle.
|
||||
// The post is guarded because a throw must not cost the close: posting on a
|
||||
// port whose background worker has been torn down throws, and the approval it
|
||||
// would have settled died with that worker, so the only thing left to do is
|
||||
// what the user asked for — go away.
|
||||
function decideSite(approved) {
|
||||
// Answer a site-connection or network-switch approval and close. The decision
|
||||
// goes out on the approval port — see approvalPort above for why — and carries
|
||||
// no approval id, because the port name already names the approval the
|
||||
// background will settle. `remember` means something only for a site
|
||||
// connection. The post is guarded because a throw must not cost the close:
|
||||
// posting on a port whose background worker has been torn down throws, and the
|
||||
// approval it would have settled died with that worker, so the only thing left
|
||||
// to do is what the user asked for — go away.
|
||||
function decide(approved, remember) {
|
||||
if (approvalPort) {
|
||||
try {
|
||||
approvalPort.postMessage({
|
||||
type: "AUTISTMASK_APPROVAL_DECISION",
|
||||
approved,
|
||||
remember: $("approve-remember").checked,
|
||||
remember,
|
||||
});
|
||||
} catch {
|
||||
// Nothing to report it to; the window closes either way.
|
||||
@@ -898,11 +925,19 @@ function init(_ctx) {
|
||||
});
|
||||
|
||||
$("btn-approve").addEventListener("click", () => {
|
||||
decideSite(true);
|
||||
decide(true, $("approve-remember").checked);
|
||||
});
|
||||
|
||||
$("btn-reject").addEventListener("click", () => {
|
||||
decideSite(false);
|
||||
decide(false, $("approve-remember").checked);
|
||||
});
|
||||
|
||||
$("btn-approve-network").addEventListener("click", () => {
|
||||
decide(true, false);
|
||||
});
|
||||
|
||||
$("btn-reject-network").addEventListener("click", () => {
|
||||
decide(false, false);
|
||||
});
|
||||
|
||||
$("btn-approve-tx").addEventListener("click", async () => {
|
||||
|
||||
@@ -51,6 +51,7 @@ const VIEWS = [
|
||||
"approve-site",
|
||||
"approve-tx",
|
||||
"approve-sign",
|
||||
"approve-network",
|
||||
"export-privkey",
|
||||
"show-phrase",
|
||||
// Shown by src/popup/views/stateRecovery.js when the stored profile
|
||||
|
||||
Reference in New Issue
Block a user