Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/@magic-ext/wallet-kit/src/MagicWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export function MagicWidget({
enableFarcaster = false,
onSuccess,
onError,
onAccountChanged,
onReady,
}: MagicWidgetProps) {
const [state, dispatch] = useReducer(widgetReducer, initialState);
Expand Down Expand Up @@ -176,6 +177,11 @@ export function MagicWidget({
}
}, []);

useEffect(() => {
getExtensionInstance().setAccountChangedCallbacks(onAccountChanged, onError);
return () => getExtensionInstance().setAccountChangedCallbacks(undefined, undefined);
}, [onAccountChanged, onError]);

useEffect(() => {
if (!clientTheme) return;

Expand Down
20 changes: 18 additions & 2 deletions packages/@magic-ext/wallet-kit/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand All @@ -233,6 +236,8 @@ export class WalletKitExtension extends Extension.Internal<'walletKit'> {
private eventsListenerAdded = false;
private reconnectPromise: Promise<void> | null = null;
private isReauthInProgress = false;
private onAccountChangedCallback?: (result: AccountChangedResult) => void;
private onAccountChangedErrorCallback?: (error: Error) => void;

constructor(options?: WalletKitExtensionOptions) {
super();
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -448,15 +453,26 @@ 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<void> {
if (this.isReauthInProgress) return;
this.isReauthInProgress = true;
try {
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;
}
Expand Down
10 changes: 9 additions & 1 deletion packages/@magic-ext/wallet-kit/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down