@abstraxn/signer-react 1.0.14 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.16] - 2026-02-23
9
+
10
+ ### Fixed
11
+
12
+ - **External wallet disconnect from extension** – When the user disconnects from an external wallet (e.g. MetaMask) via the extension ("Disconnect this site"), the app now updates `isConnected` to false immediately without requiring a page refresh. Previously the sync effect returned early when the user had explicitly connected, so state was never reset until refresh. The fix: (1) allow the sync effect to run the existing reset when wagmi reports disconnected; (2) use wagmi’s `useConnectionEffect(onDisconnect)` to clear external wallet state as soon as wagmi reports disconnect; (3) subscribe to the connector provider’s `accountsChanged` and `disconnect` events so state clears even if wagmi is slow or stale.
13
+
14
+ ## [1.0.15] - 2026-02-20
15
+
16
+ ### Added
17
+
18
+ - **`useEmailForOtp` hook** – Hook to get and set the email used to pre-fill the email-OTP form in onboarding. When the app calls `setEmailForOtp(email)`, the onboarding email field shows that value and is read-only (user cannot edit it). The same prefill and read-only behavior applies in both React onboarding (OnboardingUIReact) and Web onboarding (OnboardingUIWeb), including when opening the modal via ConnectButton. Exported from the package as `useEmailForOtp`.
19
+ - **Pre-fill email context** – Provider now exposes `emailForOtp` and `setEmailForOtp` on context. When `emailForOtp` is set, the email input is read-only and shows `cursor: not-allowed` for clearer UX.
20
+
8
21
  ## [1.0.14] - 2026-02-20
9
22
 
10
23
  ### Fixed
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * AbstraxnProviderInner - Core provider logic
4
4
  * This component contains all the wallet state management and operations
@@ -6,15 +6,22 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  import { useEffect, useState, useRef, useCallback, useMemo, } from "react";
7
7
  import { AbstraxnWallet, AuthenticationError, } from "@abstraxn/signer-core";
8
8
  import { OnboardingUIWeb } from "../OnboardingUI";
9
+ import { useConnectionEffect, } from "wagmi";
9
10
  import { parseEther, createPublicClient, http } from "viem";
10
11
  import { ExternalWalletButtons } from "../../ExternalWalletButtons";
11
12
  import { EVM_CHAINS, SOLANA_CHAINS, getChainById, toCoreChain, } from "../../chains";
12
13
  import { AbstraxnContext } from "./context";
14
+ /** Syncs wagmi disconnect to external wallet state; only rendered when wagmi is available (under WagmiProvider). */
15
+ function WagmiConnectionEffectSync({ onDisconnect, }) {
16
+ useConnectionEffect({ onDisconnect });
17
+ return null;
18
+ }
13
19
  export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
14
20
  const { isInitialized, setIsInitialized, isConnected, setIsConnected, address, setAddress, user, setUser, whoami, setWhoami, chainId, setChainId, error, setError, loading, setLoading, resending, setResending, walletBalance, setWalletBalance, onboardingRef, otpIdRef, walletRef, googleCallbackHandledRef, twitterCallbackHandledRef, discordCallbackHandledRef, } = base;
15
21
  const externalWalletsEnabled = config.externalWallets?.enabled ?? false;
16
22
  // Keep a ref to avoid re-creating callbacks when toggling config (prevents flicker)
17
23
  const externalWalletsEnabledRef = useRef(externalWalletsEnabled);
24
+ const isExternalWalletConnectedRef = useRef(false);
18
25
  useEffect(() => {
19
26
  externalWalletsEnabledRef.current = externalWalletsEnabled;
20
27
  }, [externalWalletsEnabled]);
@@ -34,8 +41,12 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
34
41
  // Connection type tracking: 'google' | 'email' | 'discord' | 'x' | 'passkey' | 'metamask' | 'walletconnect' | 'coinbase' | 'phantom' | 'injected' | null
35
42
  const [connectionType, setConnectionType] = useState(null);
36
43
  const [disconnecting, setDisconnecting] = useState(false);
44
+ const [emailForOtp, setEmailForOtpState] = useState("");
37
45
  const explicitConnectionRef = useRef(false);
38
46
  const autoDisconnectHandledRef = useRef(false);
47
+ useEffect(() => {
48
+ isExternalWalletConnectedRef.current = isExternalWalletConnected;
49
+ }, [isExternalWalletConnected]);
39
50
  // Track when we last connected to prevent premature reset
40
51
  const lastConnectionTimeRef = useRef(0);
41
52
  // Refs to track previous values and prevent unnecessary updates
@@ -1016,6 +1027,9 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
1016
1027
  onboarding.modalOverlay.classList.add("onboarding-modal-open");
1017
1028
  onboarding.modalOverlay.style.display = "flex";
1018
1029
  document.body.style.overflow = "hidden";
1030
+ if (emailForOtp && typeof onboarding.setPreFillEmail === "function") {
1031
+ onboarding.setPreFillEmail(emailForOtp, { readOnly: true });
1032
+ }
1019
1033
  // Ensure external wallet container is visible when modal opens
1020
1034
  if (externalWalletsEnabledRef.current &&
1021
1035
  onboarding.externalWalletContainer) {
@@ -1078,6 +1092,9 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
1078
1092
  onboarding.rootElement.style.display = "";
1079
1093
  }
1080
1094
  document.body.style.overflow = "hidden";
1095
+ if (emailForOtp && typeof onboarding.setPreFillEmail === "function") {
1096
+ onboarding.setPreFillEmail(emailForOtp, { readOnly: true });
1097
+ }
1081
1098
  // If on OTP screen, ensure all login elements (social buttons, etc.) are hidden
1082
1099
  if (isOtpScreen) {
1083
1100
  // On OTP screen - ensure all login elements are hidden
@@ -1155,7 +1172,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
1155
1172
  }
1156
1173
  }
1157
1174
  // Note: externalWalletsEnabled is accessed via ref to keep this callback stable
1158
- }, []);
1175
+ }, [emailForOtp]);
1159
1176
  // Initialize IndexedDB
1160
1177
  const init = useCallback(async () => {
1161
1178
  if (!walletRef.current || isInitialized)
@@ -1977,12 +1994,6 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
1977
1994
  isUpdatingRef.current = false;
1978
1995
  return;
1979
1996
  }
1980
- // If we're not connected but explicitConnectionRef is true, it means we're trying to reconnect
1981
- // Don't reset state in this case - wait for the connection to complete
1982
- if (!wagmiAccount.isConnected && explicitConnectionRef.current) {
1983
- // Connection is in progress, don't interfere
1984
- return;
1985
- }
1986
1997
  }
1987
1998
  // Check if external wallet is connected
1988
1999
  if (wagmiAccount.isConnected && wagmiAccount.address) {
@@ -2476,6 +2487,95 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
2476
2487
  setLoading(false);
2477
2488
  }
2478
2489
  }, [externalWalletsEnabled, wagmiDisconnect, wagmiAccount?.connector]);
2490
+ // When wagmi reports disconnect (e.g. user disconnected in extension), clear external wallet state immediately
2491
+ const handleWagmiDisconnect = useCallback(() => {
2492
+ if (!externalWalletsEnabledRef.current)
2493
+ return;
2494
+ setIsExternalWalletConnected(false);
2495
+ setExternalWalletAddress(null);
2496
+ setExternalWalletChainId(null);
2497
+ setConnectionType(null);
2498
+ try {
2499
+ localStorage.removeItem("abstraxn_connection_type");
2500
+ }
2501
+ catch (e) {
2502
+ // Ignore
2503
+ }
2504
+ explicitConnectionRef.current = false;
2505
+ lastConnectionTimeRef.current = 0;
2506
+ lastAddressRef.current = null;
2507
+ lastChainIdRef.current = null;
2508
+ if (!walletRef.current?.isConnected) {
2509
+ setIsConnected(false);
2510
+ setAddress(null);
2511
+ setChainId(null);
2512
+ }
2513
+ }, [
2514
+ setIsExternalWalletConnected,
2515
+ setExternalWalletAddress,
2516
+ setExternalWalletChainId,
2517
+ setConnectionType,
2518
+ setIsConnected,
2519
+ setAddress,
2520
+ setChainId,
2521
+ ]);
2522
+ // When user disconnects in the extension, provider may emit before wagmi updates; listen so we clear state even if wagmi is stale
2523
+ const providerListenerCleanupRef = useRef(null);
2524
+ useEffect(() => {
2525
+ if (!externalWalletsEnabled ||
2526
+ !isExternalWalletConnected ||
2527
+ !wagmiAccount?.connector) {
2528
+ return;
2529
+ }
2530
+ const connector = wagmiAccount.connector;
2531
+ let mounted = true;
2532
+ const setup = async () => {
2533
+ try {
2534
+ const getProvider = connector.getProvider ?? connector.getEthereumProvider;
2535
+ if (typeof getProvider !== "function")
2536
+ return;
2537
+ const p = await getProvider();
2538
+ if (!mounted || !p)
2539
+ return;
2540
+ const onDisconnectFromProvider = () => {
2541
+ if (!isExternalWalletConnectedRef.current)
2542
+ return;
2543
+ handleWagmiDisconnect();
2544
+ };
2545
+ const onAccountsChanged = (accounts) => {
2546
+ const list = Array.isArray(accounts) ? accounts : [];
2547
+ if (list.length === 0)
2548
+ onDisconnectFromProvider();
2549
+ };
2550
+ p.on?.("accountsChanged", onAccountsChanged);
2551
+ p.on?.("disconnect", onDisconnectFromProvider);
2552
+ return () => {
2553
+ p.removeListener?.("accountsChanged", onAccountsChanged);
2554
+ p.removeListener?.("disconnect", onDisconnectFromProvider);
2555
+ };
2556
+ }
2557
+ catch {
2558
+ // Connector may not support getProvider (e.g. some injected)
2559
+ return undefined;
2560
+ }
2561
+ };
2562
+ setup().then((cleanup) => {
2563
+ if (mounted)
2564
+ providerListenerCleanupRef.current = cleanup ?? null;
2565
+ });
2566
+ return () => {
2567
+ mounted = false;
2568
+ const cleanup = providerListenerCleanupRef.current;
2569
+ providerListenerCleanupRef.current = null;
2570
+ if (typeof cleanup === "function")
2571
+ cleanup();
2572
+ };
2573
+ }, [
2574
+ externalWalletsEnabled,
2575
+ isExternalWalletConnected,
2576
+ wagmiAccount?.connector,
2577
+ handleWagmiDisconnect,
2578
+ ]);
2479
2579
  // Helper function to get network name from chain ID
2480
2580
  const getNetworkName = useCallback((chainId) => {
2481
2581
  const chainNames = {
@@ -2728,6 +2828,9 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
2728
2828
  setLoading(false);
2729
2829
  }
2730
2830
  }, [isExternalWalletConnected, wagmiSwitchChain, switchChain, availableChains]);
2831
+ const setEmailForOtp = useCallback((email) => {
2832
+ setEmailForOtpState(email);
2833
+ }, []);
2731
2834
  const value = {
2732
2835
  wallet: walletRef.current,
2733
2836
  isInitialized,
@@ -2800,6 +2903,9 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
2800
2903
  walletBalance: !isExternalWalletConnected ? walletBalance : undefined,
2801
2904
  // Connection type
2802
2905
  connectionType,
2906
+ // Pre-fill email for email-OTP flow
2907
+ emailForOtp,
2908
+ setEmailForOtp: setEmailForOtp,
2803
2909
  };
2804
2910
  // Ref to store latest value to avoid dependency issues (defined after value)
2805
2911
  const valueRef = useRef(value);
@@ -3338,6 +3444,6 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
3338
3444
  console.warn("Failed to update ExternalWalletButtons:", error);
3339
3445
  }
3340
3446
  }, [value, externalWalletsEnabled]);
3341
- return (_jsx(AbstraxnContext.Provider, { value: value, children: children }));
3447
+ return (_jsxs(AbstraxnContext.Provider, { value: value, children: [wagmi ? (_jsx(WagmiConnectionEffectSync, { onDisconnect: handleWagmiDisconnect })) : null, children] }));
3342
3448
  }
3343
3449
  //# sourceMappingURL=AbstraxnProviderInner.js.map