Fix duplicate signing; make account switching an explicit action

Root cause of "requires a lot of signs, not just a single one":
inject.ts wired Phantom's own accountChanged provider event to
auto-trigger re-auth, but calling connect() ourselves also fires that
same event -- so a normal silent reconnect raced its own
event-triggered handler, producing two competing "wallet connected"
reports that each independently asked Phantom to sign a fresh nonce.

Removed that event wiring entirely (onWalletEvent, EVENT_CHANNEL) --
inject.ts now only responds to our own explicit calls, never reacts
to unsolicited provider events. Per feedback, account switching isn't
something that should be inferred from a Phantom event anyway; it's
now its own explicit feature: a "Switch account" button in the popup
(nexa:switch-account) that tells the content script to disconnect and
immediately reconnect, so Phantom's connect UI reflects whichever
account is currently active there.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B
This commit is contained in:
2026-09-07 14:02:11 +02:00
co-authored by claude
parent b93a9c6f10
commit 8d104ef3cf
9 changed files with 87 additions and 76 deletions
+15 -7
View File
@@ -1,7 +1,12 @@
import { clearSessionToken, getSessionToken, getStoredSession } from '@/background/backend-client';
import { getConnectionStatus, setConnectionStatus } from '@/background/connection-status';
import { applyLockState, getLockState } from '@/background/lock-state';
import { handleWalletConnected, requestWalletDisconnect, requestWalletReconnect } from '@/background/wallet-auth';
import {
handleWalletConnected,
requestAccountSwitch,
requestWalletDisconnect,
requestWalletReconnect,
} from '@/background/wallet-auth';
import { connectWsClient } from '@/background/ws-client';
import type { NexaMessage } from '@/shared/messaging';
@@ -39,12 +44,11 @@ export default defineBackground(() => {
case 'nexa:wallet-connected':
// A content script reports this on every page load (it always tries
// a silent onlyIfTrusted connect first), AND whenever the user
// switches accounts in Phantom's own UI. Only re-authenticate (which
// means asking Phantom to sign a fresh nonce — a popup every time,
// unlike a silent connect) when this isn't the wallet we're already
// signed in as; a bare "do we have a token" check can't tell those
// apart from an account switch.
// a silent onlyIfTrusted connect first), and after an explicit
// account switch. Only re-authenticate (which means asking Phantom
// to sign a fresh nonce — a popup every time, unlike a silent
// connect) when this isn't the wallet we're already signed in as; a
// bare "do we have a token" check can't tell those apart.
getStoredSession()
.then((session) => {
if (session?.walletAddress === message.walletAddress) return;
@@ -63,6 +67,10 @@ export default defineBackground(() => {
void signOut();
return false;
case 'nexa:switch-account':
void requestAccountSwitch();
return false;
default:
return undefined;
}
+12 -3
View File
@@ -38,6 +38,10 @@ export function App() {
void browser.runtime.sendMessage({ type: 'nexa:sign-out' } satisfies NexaMessage);
}
function switchAccount(): void {
void browser.runtime.sendMessage({ type: 'nexa:switch-account' } satisfies NexaMessage);
}
return (
<>
<header>
@@ -64,9 +68,14 @@ export function App() {
</section>
{connectionStatus === 'connected' && (
<button type="button" className="sign-out-button" onClick={signOut}>
Sign out
</button>
<div className="account-actions">
<button type="button" className="sign-out-button" onClick={switchAccount}>
Switch account
</button>
<button type="button" className="sign-out-button" onClick={signOut}>
Sign out
</button>
</div>
)}
<a
+6 -3
View File
@@ -79,10 +79,13 @@ header .subtitle {
opacity: 0.65;
}
.account-actions {
display: flex;
gap: 8px;
}
.sign-out-button {
display: block;
width: 100%;
margin-top: 4px;
flex: 1;
padding: 6px 10px;
border: 1px solid rgba(128, 128, 128, 0.4);
border-radius: 6px;