diff --git a/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx b/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx index 379e1c5a1..8bee9f435 100644 --- a/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx +++ b/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx @@ -147,6 +147,7 @@ export function MagicWidget({ enableFarcaster = false, onSuccess, onError, + onAccountChanged, onReady, }: MagicWidgetProps) { const [state, dispatch] = useReducer(widgetReducer, initialState); @@ -176,6 +177,11 @@ export function MagicWidget({ } }, []); + useEffect(() => { + getExtensionInstance().setAccountChangedCallbacks(onAccountChanged, onError); + return () => getExtensionInstance().setAccountChangedCallbacks(undefined, undefined); + }, [onAccountChanged, onError]); + useEffect(() => { if (!clientTheme) return; diff --git a/packages/@magic-ext/wallet-kit/src/extension.ts b/packages/@magic-ext/wallet-kit/src/extension.ts index 47facde1d..a850f1a21 100644 --- a/packages/@magic-ext/wallet-kit/src/extension.ts +++ b/packages/@magic-ext/wallet-kit/src/extension.ts @@ -220,6 +220,9 @@ export interface WalletKitExtensionOptions { projectId?: string; } +/** Shape passed to the onAccountChanged callback. */ +type AccountChangedResult = { method: 'wallet'; walletAddress: string }; + export class WalletKitExtension extends Extension.Internal<'walletKit'> { name = 'walletKit' as const; config = {}; @@ -233,6 +236,8 @@ export class WalletKitExtension extends Extension.Internal<'walletKit'> { private eventsListenerAdded = false; private reconnectPromise: Promise | null = null; private isReauthInProgress = false; + private onAccountChangedCallback?: (result: AccountChangedResult) => void; + private onAccountChangedErrorCallback?: (error: Error) => void; constructor(options?: WalletKitExtensionOptions) { super(); @@ -372,7 +377,7 @@ export class WalletKitExtension extends Extension.Internal<'walletKit'> { // Watch for account/chain changes using wagmi's watchAccount const unwatch = watchAccount(this.wagmiConfig, { - onChange: (account, prevAccount) => { + onChange: (account) => { const storedAddress = localStorage.getItem(LocalStorageKeys.ADDRESS); const storedChainId = localStorage.getItem(LocalStorageKeys.CHAIN_ID); @@ -448,6 +453,14 @@ export class WalletKitExtension extends Extension.Internal<'walletKit'> { * Called when the user switches accounts in their wallet while already signed in. * The wallet's native signing prompt will appear to the user. */ + public setAccountChangedCallbacks( + onAccountChanged?: (result: AccountChangedResult) => void, + onError?: (error: Error) => void, + ) { + this.onAccountChangedCallback = onAccountChanged; + this.onAccountChangedErrorCallback = onError; + } + private async performSilentReauth(address: string, chainId: number): Promise { if (this.isReauthInProgress) return; this.isReauthInProgress = true; @@ -455,8 +468,11 @@ export class WalletKitExtension extends Extension.Internal<'walletKit'> { const message = await this.generateMessage({ address, chainId }); const signature = await signMessage(this.wagmiConfig, { message }); await this.login({ message, signature }); + this.onAccountChangedCallback?.({ method: 'wallet', walletAddress: address }); } catch (err) { - console.error('Silent SIWE re-auth failed for new account:', err); + const error = err instanceof Error ? err : new Error(String(err), { cause: err }); + console.error('SIWE re-auth failed for new account:', error); + this.onAccountChangedErrorCallback?.(error); } finally { this.isReauthInProgress = false; } diff --git a/packages/@magic-ext/wallet-kit/src/types.ts b/packages/@magic-ext/wallet-kit/src/types.ts index a9336a458..2d8ebf47e 100644 --- a/packages/@magic-ext/wallet-kit/src/types.ts +++ b/packages/@magic-ext/wallet-kit/src/types.ts @@ -185,11 +185,19 @@ export interface MagicWidgetProps { onSuccess?: (result: LoginResult) => void; /** - * Callback fired when login fails + * Callback fired when login fails, or when a silent re-auth triggered by an + * account switch fails (e.g. the user rejects the signing prompt). * @example onError={(error) => console.error(error.message)} */ onError?: (error: Error) => void; + /** + * Callback fired when the user switches to a different wallet account while already signed in. + * The new SIWE session has been verified before this fires. + * @example onAccountChanged={(result) => updateUI(result.walletAddress)} + */ + onAccountChanged?: (result: WalletLoginResult) => void; + /** * Callback fired when the widget has finished initializing and is ready to display. * Use this to hide your custom loading UI.