import { browser } from 'wxt/browser'; import type { NexaMessage } from '@/shared/messaging'; import { callWallet, onWalletEvent } from './wallet-bridge/relay'; import { hideConnectBanner, setConnectBannerError, showConnectBanner } from './wallet-bridge/banner'; interface ConnectResult { walletAddress: string; } interface SignResult { signature: string; } async function reportConnected(walletAddress: string): Promise { console.debug('[nexa/wallet-connect] connected', walletAddress); hideConnectBanner(); await browser.runtime .sendMessage({ type: 'nexa:wallet-connected', walletAddress } satisfies NexaMessage) .catch((err) => console.debug('[nexa/wallet-connect] failed to notify background:', err)); } /** Wallet disconnected or switched to no account — clears the backend session (tied to the old wallet) and re-shows the connect prompt. */ async function reportDisconnected(): Promise { console.debug('[nexa/wallet-connect] disconnected'); await browser.runtime .sendMessage({ type: 'nexa:wallet-disconnected' } satisfies NexaMessage) .catch((err) => console.debug('[nexa/wallet-connect] failed to notify background:', err)); showConnectBanner(() => void connectWithGesture()); } /** Real user gesture (banner button click) — required for Phantom to show its connect approval popup on a first-ever connect. */ async function connectWithGesture(): Promise { console.debug('[nexa/wallet-connect] banner clicked, calling connect()'); try { const { walletAddress } = await callWallet('connect', {}); await reportConnected(walletAddress); } catch (err) { console.debug('[nexa/wallet-connect] connect() failed:', err); setConnectBannerError(err instanceof Error ? err.message : String(err)); } } /** * `onlyIfTrusted` succeeds without any user interaction if this origin was * already approved in a previous session — the normal case after the first * connect. Falls back to the on-page banner (a real click) only when it isn't. */ async function attemptSilentConnect(): Promise { console.debug('[nexa/wallet-connect] attempting silent connect'); try { const { walletAddress } = await callWallet('connect', { onlyIfTrusted: true }); await reportConnected(walletAddress); } catch (err) { console.debug('[nexa/wallet-connect] silent connect failed, showing banner:', err); showConnectBanner(() => void connectWithGesture()); } } /** Wires wallet connect/sign into the page. Independent of any site adapter — runs regardless of whether a buy-button adapter matched. */ export function initWalletConnect(): void { console.debug('[nexa/wallet-connect] init on', window.location.href); void attemptSilentConnect(); // Phantom's own account-switch/disconnect events — not initiated by us, so // this catches the user changing accounts (or disconnecting) directly in // Phantom's UI, not just our own sign-out flow below. onWalletEvent((name, walletAddress) => { console.debug('[nexa/wallet-connect] wallet event', name, walletAddress); if (walletAddress) { void reportConnected(walletAddress); // switched to a different account — re-auth as it } else { void reportDisconnected(); // accountChanged(null) or a 'disconnect' event } }); browser.runtime.onMessage.addListener((message: NexaMessage, _sender, sendResponse) => { if (message?.type === 'nexa:wallet-sign-request') { callWallet('signMessage', { message: message.nonce }) .then((result) => sendResponse(result)) .catch((err) => sendResponse({ error: err instanceof Error ? err.message : String(err) })); return true; } if (message?.type === 'nexa:request-wallet-connect') { void attemptSilentConnect(); return false; } if (message?.type === 'nexa:wallet-disconnect-request') { // Sign-out, initiated from the popup (see background/wallet-auth.ts). // Phantom's disconnect() revokes this origin's trust, so the next // onlyIfTrusted attempt correctly fails until the user connects again. callWallet('disconnect', {}) .catch((err) => console.debug('[nexa/wallet-connect] disconnect() failed:', err)) .finally(() => void reportDisconnected()); return false; } return undefined; }); }