signer-test-sdk-react 0.0.1

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.
Files changed (70) hide show
  1. package/README.md +114 -0
  2. package/dist/src/AbstraxnProvider.d.ts +20 -0
  3. package/dist/src/AbstraxnProvider.js +2213 -0
  4. package/dist/src/AbstraxnProvider.js.map +1 -0
  5. package/dist/src/ConnectButton.css +217 -0
  6. package/dist/src/ConnectButton.d.ts +71 -0
  7. package/dist/src/ConnectButton.js +102 -0
  8. package/dist/src/ConnectButton.js.map +1 -0
  9. package/dist/src/ExternalWalletButtons.css +319 -0
  10. package/dist/src/ExternalWalletButtons.d.ts +56 -0
  11. package/dist/src/ExternalWalletButtons.js +245 -0
  12. package/dist/src/ExternalWalletButtons.js.map +1 -0
  13. package/dist/src/OnboardingUI.d.ts +63 -0
  14. package/dist/src/OnboardingUI.js +66 -0
  15. package/dist/src/OnboardingUI.js.map +1 -0
  16. package/dist/src/WalletModal.css +549 -0
  17. package/dist/src/WalletModal.d.ts +6 -0
  18. package/dist/src/WalletModal.js +89 -0
  19. package/dist/src/WalletModal.js.map +1 -0
  20. package/dist/src/components/OnboardingUI/OnboardingUI.css +727 -0
  21. package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +15 -0
  22. package/dist/src/components/OnboardingUI/OnboardingUIReact.js +65 -0
  23. package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -0
  24. package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +257 -0
  25. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +3454 -0
  26. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -0
  27. package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +16 -0
  28. package/dist/src/components/OnboardingUI/components/EmailForm.js +19 -0
  29. package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -0
  30. package/dist/src/components/OnboardingUI/components/Modal.d.ts +15 -0
  31. package/dist/src/components/OnboardingUI/components/Modal.js +68 -0
  32. package/dist/src/components/OnboardingUI/components/Modal.js.map +1 -0
  33. package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +19 -0
  34. package/dist/src/components/OnboardingUI/components/OtpForm.js +58 -0
  35. package/dist/src/components/OnboardingUI/components/OtpForm.js.map +1 -0
  36. package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +14 -0
  37. package/dist/src/components/OnboardingUI/components/PasskeyButton.js +22 -0
  38. package/dist/src/components/OnboardingUI/components/PasskeyButton.js.map +1 -0
  39. package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +15 -0
  40. package/dist/src/components/OnboardingUI/components/SocialButtons.js +15 -0
  41. package/dist/src/components/OnboardingUI/components/SocialButtons.js.map +1 -0
  42. package/dist/src/components/OnboardingUI/components/index.d.ts +13 -0
  43. package/dist/src/components/OnboardingUI/components/index.js +9 -0
  44. package/dist/src/components/OnboardingUI/components/index.js.map +1 -0
  45. package/dist/src/components/OnboardingUI/hooks/index.d.ts +7 -0
  46. package/dist/src/components/OnboardingUI/hooks/index.js +6 -0
  47. package/dist/src/components/OnboardingUI/hooks/index.js.map +1 -0
  48. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.d.ts +11 -0
  49. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +146 -0
  50. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -0
  51. package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +21 -0
  52. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +135 -0
  53. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -0
  54. package/dist/src/components/OnboardingUI/index.d.ts +12 -0
  55. package/dist/src/components/OnboardingUI/index.js +15 -0
  56. package/dist/src/components/OnboardingUI/index.js.map +1 -0
  57. package/dist/src/hooks.d.ts +204 -0
  58. package/dist/src/hooks.js +394 -0
  59. package/dist/src/hooks.js.map +1 -0
  60. package/dist/src/index.d.ts +14 -0
  61. package/dist/src/index.js +11 -0
  62. package/dist/src/index.js.map +1 -0
  63. package/dist/src/types.d.ts +181 -0
  64. package/dist/src/types.js +2 -0
  65. package/dist/src/types.js.map +1 -0
  66. package/dist/src/wagmiConfig.d.ts +147 -0
  67. package/dist/src/wagmiConfig.js +81 -0
  68. package/dist/src/wagmiConfig.js.map +1 -0
  69. package/dist/tsconfig.tsbuildinfo +1 -0
  70. package/package.json +68 -0
@@ -0,0 +1,2213 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Abstraxn Wallet Provider - React Context Provider
4
+ * Wrap your app with this provider to use Abstraxn Wallet SDK
5
+ */
6
+ import { createContext, useContext, useEffect, useState, useRef, useCallback, useMemo } from 'react';
7
+ import { AbstraxnWallet, AuthenticationError } from 'signer-test-sdk-core';
8
+ import { OnboardingUIWeb } from './components/OnboardingUI';
9
+ import { WagmiProvider, useAccount, useConnect, useDisconnect, useSignMessage, useSendTransaction, useSwitchChain, useBalance, useChainId } from 'wagmi';
10
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
11
+ import { createWagmiConfig } from './wagmiConfig';
12
+ import { parseEther } from 'viem';
13
+ import { ExternalWalletButtons } from './ExternalWalletButtons';
14
+ export const AbstraxnContext = createContext(null);
15
+ // Create a default QueryClient for wagmi
16
+ const defaultQueryClient = new QueryClient({
17
+ defaultOptions: {
18
+ queries: {
19
+ refetchOnWindowFocus: false,
20
+ retry: false,
21
+ },
22
+ },
23
+ });
24
+ // Base provider logic (shared between with and without wagmi)
25
+ function useAbstraxnProviderBase(_config) {
26
+ const [isInitialized, setIsInitialized] = useState(false);
27
+ const [isConnected, setIsConnected] = useState(false);
28
+ const [address, setAddress] = useState(null);
29
+ const [user, setUser] = useState(null);
30
+ const [whoami, setWhoami] = useState(null);
31
+ const [chainId, setChainId] = useState(null);
32
+ const [error, setError] = useState(null);
33
+ const [loading, setLoading] = useState(false);
34
+ const onboardingRef = useRef(null);
35
+ const otpIdRef = useRef(null);
36
+ const walletRef = useRef(null);
37
+ const googleCallbackHandledRef = useRef(false);
38
+ const twitterCallbackHandledRef = useRef(false);
39
+ const discordCallbackHandledRef = useRef(false);
40
+ return {
41
+ isInitialized, setIsInitialized,
42
+ isConnected, setIsConnected,
43
+ address, setAddress,
44
+ user, setUser,
45
+ whoami, setWhoami,
46
+ chainId, setChainId,
47
+ error, setError,
48
+ loading, setLoading,
49
+ onboardingRef, otpIdRef, walletRef, googleCallbackHandledRef, twitterCallbackHandledRef, discordCallbackHandledRef,
50
+ };
51
+ }
52
+ // Internal component WITH wagmi hooks (used when external wallets are enabled)
53
+ function AbstraxnProviderWithWagmi({ config, children }) {
54
+ const base = useAbstraxnProviderBase(config);
55
+ const externalWalletsEnabled = config.externalWallets?.enabled ?? false;
56
+ // Keep a ref so callbacks remain stable when config toggles
57
+ const externalWalletsEnabledRef = useRef(externalWalletsEnabled);
58
+ useEffect(() => {
59
+ externalWalletsEnabledRef.current = externalWalletsEnabled;
60
+ }, [externalWalletsEnabled]);
61
+ // Always call wagmi hooks (we're inside WagmiProvider)
62
+ const wagmiAccount = useAccount();
63
+ const wagmiConnect = useConnect();
64
+ const wagmiDisconnect = useDisconnect();
65
+ const wagmiSignMessage = useSignMessage();
66
+ const wagmiSendTransaction = useSendTransaction();
67
+ const wagmiSwitchChain = useSwitchChain();
68
+ const wagmiChainIdHook = useChainId();
69
+ // useBalance will automatically refetch when address or chainId changes
70
+ const wagmiBalance = useBalance({
71
+ address: wagmiAccount.address,
72
+ chainId: wagmiChainIdHook,
73
+ query: {
74
+ enabled: !!wagmiAccount.address && wagmiAccount.isConnected,
75
+ refetchInterval: false,
76
+ },
77
+ });
78
+ return _jsx(AbstraxnProviderInner, { config: config, children: children, base: base, wagmi: { wagmiAccount, wagmiConnect, wagmiDisconnect, wagmiSignMessage, wagmiSendTransaction, wagmiSwitchChain, wagmiBalance, wagmiChainIdHook } });
79
+ }
80
+ // Internal component WITHOUT wagmi hooks (used when external wallets are disabled)
81
+ function AbstraxnProviderWithoutWagmi({ config, children }) {
82
+ const base = useAbstraxnProviderBase(config);
83
+ return _jsx(AbstraxnProviderInner, { config: config, children: children, base: base, wagmi: null });
84
+ }
85
+ function AbstraxnProviderInner({ config, children, base, wagmi }) {
86
+ const { isInitialized, setIsInitialized, isConnected, setIsConnected, address, setAddress, user, setUser, whoami, setWhoami, chainId, setChainId, error, setError, loading, setLoading, onboardingRef, otpIdRef, walletRef, googleCallbackHandledRef, twitterCallbackHandledRef, discordCallbackHandledRef, } = base;
87
+ const externalWalletsEnabled = config.externalWallets?.enabled ?? false;
88
+ // Keep a ref to avoid re-creating callbacks when toggling config (prevents flicker)
89
+ const externalWalletsEnabledRef = useRef(externalWalletsEnabled);
90
+ useEffect(() => {
91
+ externalWalletsEnabledRef.current = externalWalletsEnabled;
92
+ }, [externalWalletsEnabled]);
93
+ const wagmiAccount = wagmi?.wagmiAccount || null;
94
+ const wagmiConnect = wagmi?.wagmiConnect || null;
95
+ const wagmiDisconnect = wagmi?.wagmiDisconnect || null;
96
+ const wagmiSignMessage = wagmi?.wagmiSignMessage || null;
97
+ const wagmiSendTransaction = wagmi?.wagmiSendTransaction || null;
98
+ const wagmiSwitchChain = wagmi?.wagmiSwitchChain || null;
99
+ const wagmiBalance = wagmi?.wagmiBalance || null;
100
+ const wagmiChainIdHook = wagmi?.wagmiChainIdHook || null;
101
+ // External wallet state
102
+ const [isExternalWalletConnected, setIsExternalWalletConnected] = useState(false);
103
+ const [externalWalletAddress, setExternalWalletAddress] = useState(null);
104
+ const [externalWalletChainId, setExternalWalletChainId] = useState(null);
105
+ const explicitConnectionRef = useRef(false);
106
+ const autoDisconnectHandledRef = useRef(false);
107
+ // Track when we last connected to prevent premature reset
108
+ const lastConnectionTimeRef = useRef(0);
109
+ // Refs to track previous values and prevent unnecessary updates
110
+ const lastAddressRef = useRef(null);
111
+ const lastChainIdRef = useRef(null);
112
+ const isUpdatingRef = useRef(false);
113
+ const connectionTimeoutRef = useRef(null);
114
+ // Track if we're in the middle of a disconnect to prevent useEffect from interfering
115
+ const disconnectingRef = useRef(false);
116
+ // Show/hide external wallet UI when config changes (prevents modal flicker)
117
+ useEffect(() => {
118
+ const onboardingAny = onboardingRef.current;
119
+ if (!onboardingAny)
120
+ return;
121
+ requestAnimationFrame(() => {
122
+ if (externalWalletsEnabled) {
123
+ // Show external wallet container when enabled
124
+ if (onboardingAny.externalWalletContainer) {
125
+ onboardingAny.externalWalletContainer.style.display = '';
126
+ }
127
+ // Show divider if email/Google are also visible
128
+ const authMethods = onboardingAny.config?.authMethods || ['otp', 'google'];
129
+ const showEmail = authMethods.includes('otp');
130
+ const showGoogle = authMethods.includes('google');
131
+ const hasEmailOrGoogle = showEmail || showGoogle;
132
+ if (onboardingAny.externalWalletDivider && hasEmailOrGoogle) {
133
+ onboardingAny.externalWalletDivider.style.display = '';
134
+ }
135
+ }
136
+ else {
137
+ // Hide external wallet container when disabled
138
+ if (onboardingAny.externalWalletContainer) {
139
+ onboardingAny.externalWalletContainer.style.display = 'none';
140
+ }
141
+ if (onboardingAny.externalWalletDivider) {
142
+ onboardingAny.externalWalletDivider.style.display = 'none';
143
+ }
144
+ }
145
+ });
146
+ }, [externalWalletsEnabled]);
147
+ // Initialize wallet
148
+ useEffect(() => {
149
+ // If wallet already exists (from previous mount), reuse it to prevent flicker
150
+ if (walletRef.current) {
151
+ return;
152
+ }
153
+ const walletInstance = new AbstraxnWallet({
154
+ apiKey: config.apiKey,
155
+ authMethods: config.authMethods,
156
+ googleClientId: config.googleClientId,
157
+ defaultChainId: config.defaultChainId,
158
+ supportedChains: config.supportedChains,
159
+ autoConnect: config.autoConnect ?? false,
160
+ enableLogging: config.enableLogging ?? false,
161
+ });
162
+ walletRef.current = walletInstance;
163
+ // Set up event listeners
164
+ walletInstance.on('connect', async () => {
165
+ try {
166
+ // Verify whoami exists before setting connected
167
+ const whoamiInfo = await walletInstance.getWhoami();
168
+ if (!whoamiInfo) {
169
+ // If whoami is not available, don't set connected
170
+ return;
171
+ }
172
+ setIsConnected(true);
173
+ const userInfo = await walletInstance.getUserInfo();
174
+ setUser(userInfo);
175
+ setWhoami(whoamiInfo);
176
+ // Try to get address, but don't fail if not available yet
177
+ try {
178
+ const addr = await walletInstance.getAddress();
179
+ setAddress(addr);
180
+ }
181
+ catch (addrErr) {
182
+ console.warn('Address not available on connect:', addrErr);
183
+ }
184
+ const cid = await walletInstance.getChainId();
185
+ setChainId(cid);
186
+ }
187
+ catch (err) {
188
+ console.error('Error getting wallet info on connect:', err);
189
+ }
190
+ });
191
+ walletInstance.on('disconnect', () => {
192
+ setIsConnected(false);
193
+ setAddress(null);
194
+ setUser(null);
195
+ setWhoami(null);
196
+ setChainId(null);
197
+ });
198
+ walletInstance.on('accountChanged', (newAddress) => {
199
+ setAddress(newAddress);
200
+ });
201
+ walletInstance.on('chainChanged', (newChainId) => {
202
+ setChainId(newChainId);
203
+ });
204
+ // Initialize OnboardingUI only if not already created (prevents flicker on config change)
205
+ if (onboardingRef.current) {
206
+ return;
207
+ }
208
+ const uiConfig = config.ui || {};
209
+ const onboarding = new OnboardingUIWeb({
210
+ logo: uiConfig.logo,
211
+ theme: uiConfig.theme || 'light',
212
+ showFooter: uiConfig.showFooter !== false,
213
+ onboardTitle: uiConfig.onboardTitle || 'Sign in',
214
+ modal: uiConfig.modal !== false,
215
+ closeOnBackdropClick: uiConfig.closeOnBackdropClick !== false,
216
+ showCloseButton: uiConfig.showCloseButton !== false,
217
+ className: uiConfig.className,
218
+ style: uiConfig.style,
219
+ labels: uiConfig.labels,
220
+ colors: uiConfig.colors,
221
+ customCSS: uiConfig.customCSS,
222
+ authMethods: uiConfig.authMethods || config.authMethods,
223
+ onEmailOtpInitiate: async (email) => {
224
+ try {
225
+ if (!walletInstance)
226
+ throw new Error('Wallet not initialized');
227
+ const authManager = walletInstance.getAuthManager();
228
+ const result = await authManager.loginWithOTP(email);
229
+ otpIdRef.current = result.otpId;
230
+ }
231
+ catch (err) {
232
+ console.error('OTP Init Error:', err);
233
+ throw err;
234
+ }
235
+ },
236
+ onEmailOtpVerify: async (_email, otp) => {
237
+ try {
238
+ if (!walletInstance)
239
+ throw new Error('Wallet not initialized');
240
+ if (!otpIdRef.current)
241
+ throw new Error('OTP ID not found');
242
+ const authManager = walletInstance.getAuthManager();
243
+ const user = await authManager.verifyOTP(otpIdRef.current, otp);
244
+ otpIdRef.current = null;
245
+ // Connect wallet after successful authentication
246
+ await walletInstance.connect();
247
+ // Clear any previous errors on successful verification
248
+ setError(null);
249
+ return { success: true, user };
250
+ }
251
+ catch (err) {
252
+ console.error('OTP Verify Error:', err);
253
+ return { success: false };
254
+ }
255
+ },
256
+ onGoogleLogin: async () => {
257
+ if (!walletInstance)
258
+ throw new Error('Wallet not initialized');
259
+ const authManager = walletInstance.getAuthManager();
260
+ await authManager.loginWithGoogle();
261
+ },
262
+ onTwitterLogin: async () => {
263
+ if (!walletInstance)
264
+ throw new Error('Wallet not initialized');
265
+ const authManager = walletInstance.getAuthManager();
266
+ await authManager.loginWithTwitter();
267
+ },
268
+ onDiscordLogin: async () => {
269
+ if (!walletInstance)
270
+ throw new Error('Wallet not initialized');
271
+ const authManager = walletInstance.getAuthManager();
272
+ await authManager.loginWithDiscord();
273
+ },
274
+ onPasskeyLogin: async () => {
275
+ if (!walletInstance)
276
+ throw new Error('Wallet not initialized');
277
+ const authManager = walletInstance.getAuthManager();
278
+ const user = await authManager.loginWithPasskey();
279
+ // Set user immediately so onLoginSuccess can access it
280
+ setUser(user);
281
+ // Connect wallet after successful authentication
282
+ await walletInstance.connect();
283
+ },
284
+ onPasskeySignup: async () => {
285
+ if (!walletInstance)
286
+ throw new Error('Wallet not initialized');
287
+ const authManager = walletInstance.getAuthManager();
288
+ const user = await authManager.signupWithPasskey();
289
+ // Set user immediately so onLoginSuccess can access it
290
+ setUser(user);
291
+ // Connect wallet after successful authentication
292
+ await walletInstance.connect();
293
+ },
294
+ onLoginSuccess: async (data) => {
295
+ // Clear any previous errors on successful login
296
+ setError(null);
297
+ // Hide onboarding UI
298
+ if (onboardingRef.current) {
299
+ const onboardingAny = onboardingRef.current;
300
+ if (onboardingAny.modalOverlay) {
301
+ onboardingAny.modalOverlay.classList.remove('onboarding-modal-open');
302
+ onboardingAny.modalOverlay.classList.add('onboarding-modal-closing');
303
+ setTimeout(() => {
304
+ if (onboardingAny.modalOverlay) {
305
+ onboardingAny.modalOverlay.style.display = 'none';
306
+ }
307
+ }, 200);
308
+ }
309
+ document.body.style.overflow = '';
310
+ }
311
+ // Set connected state
312
+ setIsConnected(true);
313
+ // Set user if provided in data
314
+ if (data.user) {
315
+ setUser(data.user);
316
+ }
317
+ // Load whoami after successful login
318
+ if (walletInstance) {
319
+ try {
320
+ const whoamiInfo = await walletInstance.getWhoami();
321
+ setWhoami(whoamiInfo);
322
+ const addr = await walletInstance.getAddress();
323
+ setAddress(addr);
324
+ const cid = await walletInstance.getChainId();
325
+ setChainId(cid);
326
+ }
327
+ catch (err) {
328
+ console.error('Error loading whoami after login:', err);
329
+ }
330
+ }
331
+ },
332
+ onLoginError: (err) => {
333
+ setError(err);
334
+ },
335
+ }, externalWalletsEnabled); // Pass externalWalletsEnabled as second parameter
336
+ // Inject custom CSS if provided
337
+ if (uiConfig.customCSS) {
338
+ const styleId = 'abstraxn-custom-css';
339
+ if (!document.getElementById(styleId)) {
340
+ const style = document.createElement('style');
341
+ style.id = styleId;
342
+ style.textContent = uiConfig.customCSS;
343
+ document.head.appendChild(style);
344
+ }
345
+ }
346
+ const hasAuthParams = (params) => params.get('success') === 'true' ||
347
+ params.get('error') ||
348
+ params.get('code') ||
349
+ params.get('accessToken');
350
+ const matchesProvider = (provider, params) => {
351
+ const providerParam = (params.get('provider') || params.get('authProvider') || '').toLowerCase();
352
+ const path = window.location.pathname.toLowerCase();
353
+ if (providerParam) {
354
+ if (provider === 'twitter') {
355
+ return providerParam === 'twitter' || providerParam === 'x';
356
+ }
357
+ return providerParam === provider;
358
+ }
359
+ if (provider === 'twitter') {
360
+ return path.includes('/twitter') || path.includes('/x');
361
+ }
362
+ return path.includes(`/${provider}`);
363
+ };
364
+ onboarding.init();
365
+ // Check if we should keep it open (if handling a callback)
366
+ const urlParams = new URLSearchParams(window.location.search);
367
+ const shouldKeepOpen = hasAuthParams(urlParams);
368
+ const hasSuccess = urlParams.get('success') === 'true';
369
+ // Hide immediately - use synchronous approach to prevent flicker
370
+ // Only hide if NOT handling a callback
371
+ const onboardingAny = onboarding;
372
+ if (!shouldKeepOpen && onboardingAny.modalOverlay) {
373
+ // Hide immediately without delay to prevent flicker
374
+ onboardingAny.modalOverlay.classList.remove('onboarding-modal-open');
375
+ onboardingAny.modalOverlay.style.display = 'none';
376
+ document.body.style.overflow = '';
377
+ }
378
+ else if (hasSuccess && onboardingAny.modalOverlay) {
379
+ // If success=true is in URL, ensure modal is open to show loading
380
+ onboardingAny.modalOverlay.classList.remove('onboarding-modal-closing');
381
+ onboardingAny.modalOverlay.classList.add('onboarding-modal-open');
382
+ onboardingAny.modalOverlay.style.display = 'flex';
383
+ document.body.style.overflow = 'hidden';
384
+ }
385
+ onboardingRef.current = onboarding;
386
+ // Handle Google OAuth callback (only in useEffect, not exposed)
387
+ const handleGoogleCallback = async () => {
388
+ if (googleCallbackHandledRef.current)
389
+ return;
390
+ const urlParams = new URLSearchParams(window.location.search);
391
+ if (!hasAuthParams(urlParams) || matchesProvider('twitter', urlParams) || matchesProvider('discord', urlParams)) {
392
+ return;
393
+ }
394
+ // Show loading modal if success=true is in URL
395
+ const hasSuccess = urlParams.get('success') === 'true';
396
+ if (hasSuccess && onboardingRef.current) {
397
+ const onboardingAny = onboardingRef.current;
398
+ if (onboardingAny.showLoadingScreen) {
399
+ onboardingAny.showLoadingScreen();
400
+ }
401
+ else if (onboardingAny.showLoadingModal) {
402
+ onboardingAny.showLoadingModal();
403
+ }
404
+ }
405
+ googleCallbackHandledRef.current = true;
406
+ try {
407
+ const user = await walletInstance.handleGoogleCallback();
408
+ if (user) {
409
+ setUser(user);
410
+ window.history.replaceState({}, document.title, window.location.pathname);
411
+ // Hide loading modal and close modal on success
412
+ if (onboardingRef.current) {
413
+ const onboardingAny = onboardingRef.current;
414
+ if (onboardingAny.hideLoadingModal) {
415
+ onboardingAny.hideLoadingModal();
416
+ }
417
+ onboardingRef.current.close();
418
+ }
419
+ }
420
+ }
421
+ catch (err) {
422
+ console.error('Google callback error:', err);
423
+ // Hide loading modal on error
424
+ if (onboardingRef.current) {
425
+ const onboardingAny = onboardingRef.current;
426
+ if (onboardingAny.hideLoadingModal) {
427
+ onboardingAny.hideLoadingModal();
428
+ }
429
+ // Show error modal/screen
430
+ const errorMessage = err instanceof Error ? err.message : 'Google authentication failed';
431
+ if (onboardingAny.showError) {
432
+ onboardingAny.showError(errorMessage);
433
+ }
434
+ }
435
+ setError(err instanceof Error ? err : new Error('Google callback failed'));
436
+ googleCallbackHandledRef.current = false;
437
+ }
438
+ };
439
+ const handleDiscordCallback = async () => {
440
+ if (discordCallbackHandledRef.current)
441
+ return;
442
+ const urlParams = new URLSearchParams(window.location.search);
443
+ if (!hasAuthParams(urlParams) || !matchesProvider('discord', urlParams)) {
444
+ return;
445
+ }
446
+ // Show loading modal if success=true is in URL
447
+ const hasSuccess = urlParams.get('success') === 'true';
448
+ if (hasSuccess && onboardingRef.current) {
449
+ const onboardingAny = onboardingRef.current;
450
+ if (onboardingAny.showLoadingScreen) {
451
+ onboardingAny.showLoadingScreen();
452
+ }
453
+ else if (onboardingAny.showLoadingModal) {
454
+ onboardingAny.showLoadingModal();
455
+ }
456
+ }
457
+ discordCallbackHandledRef.current = true;
458
+ try {
459
+ const user = await walletInstance.handleDiscordCallback();
460
+ if (user) {
461
+ setUser(user);
462
+ window.history.replaceState({}, document.title, window.location.pathname);
463
+ // Hide loading modal and close modal on success
464
+ if (onboardingRef.current) {
465
+ const onboardingAny = onboardingRef.current;
466
+ if (onboardingAny.hideLoadingModal) {
467
+ onboardingAny.hideLoadingModal();
468
+ }
469
+ onboardingRef.current.close();
470
+ }
471
+ }
472
+ }
473
+ catch (err) {
474
+ console.error('Discord callback error:', err);
475
+ // Hide loading modal on error
476
+ if (onboardingRef.current) {
477
+ const onboardingAny = onboardingRef.current;
478
+ if (onboardingAny.hideLoadingModal) {
479
+ onboardingAny.hideLoadingModal();
480
+ }
481
+ // Show error modal/screen
482
+ const errorMessage = err instanceof Error ? err.message : 'Discord authentication failed';
483
+ if (onboardingAny.showError) {
484
+ onboardingAny.showError(errorMessage);
485
+ }
486
+ }
487
+ setError(err instanceof Error ? err : new Error('Discord callback failed'));
488
+ discordCallbackHandledRef.current = false;
489
+ }
490
+ };
491
+ const handleTwitterCallback = async () => {
492
+ if (twitterCallbackHandledRef.current)
493
+ return;
494
+ const urlParams = new URLSearchParams(window.location.search);
495
+ if (!hasAuthParams(urlParams) || !matchesProvider('twitter', urlParams)) {
496
+ return;
497
+ }
498
+ // Show loading modal if success=true is in URL
499
+ const hasSuccess = urlParams.get('success') === 'true';
500
+ if (hasSuccess && onboardingRef.current) {
501
+ const onboardingAny = onboardingRef.current;
502
+ if (onboardingAny.showLoadingScreen) {
503
+ onboardingAny.showLoadingScreen();
504
+ }
505
+ else if (onboardingAny.showLoadingModal) {
506
+ onboardingAny.showLoadingModal();
507
+ }
508
+ }
509
+ twitterCallbackHandledRef.current = true;
510
+ try {
511
+ const user = await walletInstance.handleTwitterCallback();
512
+ if (user) {
513
+ setUser(user);
514
+ window.history.replaceState({}, document.title, window.location.pathname);
515
+ // Hide loading modal and close modal on success
516
+ if (onboardingRef.current) {
517
+ const onboardingAny = onboardingRef.current;
518
+ if (onboardingAny.hideLoadingModal) {
519
+ onboardingAny.hideLoadingModal();
520
+ }
521
+ onboardingRef.current.close();
522
+ }
523
+ }
524
+ }
525
+ catch (err) {
526
+ console.error('Twitter callback error:', err);
527
+ // Hide loading modal on error
528
+ if (onboardingRef.current) {
529
+ const onboardingAny = onboardingRef.current;
530
+ if (onboardingAny.hideLoadingModal) {
531
+ onboardingAny.hideLoadingModal();
532
+ }
533
+ // Show error modal/screen
534
+ const errorMessage = err instanceof Error ? err.message : 'Twitter authentication failed';
535
+ if (onboardingAny.showError) {
536
+ onboardingAny.showError(errorMessage);
537
+ }
538
+ }
539
+ setError(err instanceof Error ? err : new Error('Twitter callback failed'));
540
+ twitterCallbackHandledRef.current = false;
541
+ }
542
+ };
543
+ handleGoogleCallback();
544
+ handleDiscordCallback();
545
+ handleTwitterCallback();
546
+ // Helper function to restore connection state
547
+ const restoreConnectionState = async (wallet) => {
548
+ try {
549
+ // Verify whoami exists before setting connected
550
+ const whoamiInfo = await wallet.getWhoami();
551
+ if (!whoamiInfo) {
552
+ // If whoami is not available, don't set connected
553
+ return;
554
+ }
555
+ setIsConnected(true);
556
+ const userInfo = await wallet.getUserInfo();
557
+ setUser(userInfo);
558
+ setWhoami(whoamiInfo);
559
+ // Try to get address, but don't fail if not available yet
560
+ try {
561
+ const addr = await wallet.getAddress();
562
+ setAddress(addr);
563
+ }
564
+ catch (addrErr) {
565
+ console.warn('Address not available during restore:', addrErr);
566
+ }
567
+ const cid = await wallet.getChainId();
568
+ setChainId(cid);
569
+ }
570
+ catch (err) {
571
+ console.error('Error restoring connection state:', err);
572
+ // If restore fails, mark as disconnected
573
+ setIsConnected(false);
574
+ }
575
+ };
576
+ // Auto-initialize if enabled
577
+ if (config.autoInit !== false) {
578
+ init().then(() => {
579
+ // After initialization, check if wallet is already connected (from localStorage)
580
+ // The wallet's initialize() method already sets isConnected if authenticated
581
+ // But we need to restore React state if the event was missed
582
+ if (walletInstance && walletInstance.isConnected) {
583
+ // Wallet is already connected, restore state
584
+ restoreConnectionState(walletInstance);
585
+ }
586
+ });
587
+ }
588
+ return () => {
589
+ if (onboardingRef.current) {
590
+ onboardingRef.current.destroy();
591
+ }
592
+ };
593
+ }, []); // Only run once on mount
594
+ // Ref to store the root instance to prevent re-mounting (defined early)
595
+ const externalWalletRootRef = useRef(null);
596
+ const externalWalletMountedRef = useRef(false);
597
+ const externalWalletContainerRef = useRef(null);
598
+ // Show onboarding UI (defined first to avoid hoisting issues)
599
+ const showOnboarding = useCallback(() => {
600
+ setError(null); // Clear any previous errors
601
+ if (onboardingRef.current) {
602
+ const onboarding = onboardingRef.current;
603
+ // Re-initialize if destroyed
604
+ if (!onboarding.modalOverlay || !document.body.contains(onboarding.modalOverlay)) {
605
+ onboarding.init();
606
+ // Wait for init to complete
607
+ setTimeout(() => {
608
+ if (onboarding.modalOverlay) {
609
+ // Use requestAnimationFrame to prevent blinking
610
+ requestAnimationFrame(() => {
611
+ onboarding.modalOverlay.classList.remove('onboarding-modal-closing');
612
+ onboarding.modalOverlay.classList.add('onboarding-modal-open');
613
+ onboarding.modalOverlay.style.display = 'flex';
614
+ document.body.style.overflow = 'hidden';
615
+ // Ensure external wallet container is visible when modal opens
616
+ if (externalWalletsEnabledRef.current && onboarding.externalWalletContainer) {
617
+ // Check if we're on OTP screen - if so, don't show external wallets
618
+ // Check if OTP screen exists AND is actually visible in the DOM
619
+ const isOtpScreen = onboarding.otpVerificationScreen &&
620
+ onboarding.otpVerificationScreen.parentElement &&
621
+ onboarding.otpVerificationScreen.offsetParent !== null;
622
+ if (!isOtpScreen) {
623
+ // Only show external wallets if not on OTP screen
624
+ onboarding.externalWalletContainer.style.display = '';
625
+ // Show divider only if both email/Google AND external wallets are visible
626
+ const authMethods = onboarding.config?.authMethods || ['otp', 'google'];
627
+ const showEmail = authMethods.includes('otp');
628
+ const showGoogle = authMethods.includes('google');
629
+ const hasEmailOrGoogle = showEmail || showGoogle;
630
+ if (onboarding.externalWalletDivider && hasEmailOrGoogle) {
631
+ onboarding.externalWalletDivider.style.display = '';
632
+ }
633
+ else if (onboarding.externalWalletDivider) {
634
+ onboarding.externalWalletDivider.style.display = 'none';
635
+ }
636
+ }
637
+ else {
638
+ // On OTP screen - hide external wallets
639
+ onboarding.externalWalletContainer.style.display = 'none';
640
+ if (onboarding.externalWalletDivider) {
641
+ onboarding.externalWalletDivider.style.display = 'none';
642
+ }
643
+ }
644
+ // Always re-render external wallets when modal opens immediately
645
+ setTimeout(() => {
646
+ const container = onboarding.externalWalletContainer;
647
+ if (!container) {
648
+ return;
649
+ }
650
+ // Use the reusable mount function
651
+ mountExternalWalletsToContainer(container, onboarding);
652
+ }, 50); // Reduced from 200ms to 50ms
653
+ }
654
+ });
655
+ }
656
+ }, 50);
657
+ }
658
+ else {
659
+ // Modal already exists, just show it
660
+ // Use requestAnimationFrame to prevent blinking
661
+ requestAnimationFrame(() => {
662
+ onboarding.modalOverlay.classList.remove('onboarding-modal-closing');
663
+ onboarding.modalOverlay.classList.add('onboarding-modal-open');
664
+ onboarding.modalOverlay.style.display = 'flex';
665
+ if (onboarding.rootElement) {
666
+ onboarding.rootElement.style.display = '';
667
+ }
668
+ document.body.style.overflow = 'hidden';
669
+ // CRITICAL: Always ensure external wallets are mounted when modal reopens
670
+ if (externalWalletsEnabledRef.current && onboarding.externalWalletContainer) {
671
+ // Check if we're on OTP screen - if so, don't show external wallets
672
+ const isOtpScreen = onboarding.otpVerificationScreen &&
673
+ onboarding.otpVerificationScreen.parentElement;
674
+ if (!isOtpScreen) {
675
+ // Only show external wallets if not on OTP screen
676
+ onboarding.externalWalletContainer.style.display = '';
677
+ // Show divider only if both email/Google AND external wallets are visible
678
+ const authMethods = onboarding.config?.authMethods || ['otp', 'google'];
679
+ const showEmail = authMethods.includes('otp');
680
+ const showGoogle = authMethods.includes('google');
681
+ const hasEmailOrGoogle = showEmail || showGoogle;
682
+ if (onboarding.externalWalletDivider && hasEmailOrGoogle) {
683
+ onboarding.externalWalletDivider.style.display = '';
684
+ }
685
+ else if (onboarding.externalWalletDivider) {
686
+ onboarding.externalWalletDivider.style.display = 'none';
687
+ }
688
+ }
689
+ else {
690
+ // On OTP screen - hide external wallets
691
+ onboarding.externalWalletContainer.style.display = 'none';
692
+ if (onboarding.externalWalletDivider) {
693
+ onboarding.externalWalletDivider.style.display = 'none';
694
+ }
695
+ }
696
+ // Always re-render external wallets when modal opens (even if already mounted)
697
+ // This ensures they're visible after modal close/reopen
698
+ setTimeout(() => {
699
+ const container = onboarding.externalWalletContainer;
700
+ if (!container) {
701
+ return;
702
+ }
703
+ // Use the reusable mount function (will be defined later, but accessible via closure)
704
+ if (typeof mountExternalWalletsToContainer === 'function') {
705
+ mountExternalWalletsToContainer(container, onboarding);
706
+ }
707
+ }, 50); // Reduced delay for faster initial render
708
+ }
709
+ });
710
+ }
711
+ }
712
+ // Note: externalWalletsEnabled is accessed via ref to keep this callback stable
713
+ }, []);
714
+ // Initialize IndexedDB
715
+ const init = useCallback(async () => {
716
+ if (!walletRef.current || isInitialized)
717
+ return;
718
+ setLoading(true);
719
+ setError(null);
720
+ try {
721
+ const authManager = walletRef.current.getAuthManager();
722
+ const turnkeyService = authManager.turnkeyService;
723
+ await turnkeyService.init();
724
+ setIsInitialized(true);
725
+ // Try to connect wallet to check if user is already authenticated (from localStorage)
726
+ // This will trigger the wallet's initialize() which checks localStorage
727
+ try {
728
+ await walletRef.current.connect();
729
+ // If connect succeeds, the 'connect' event will be emitted
730
+ // But also manually restore state in case event was missed
731
+ if (walletRef.current && walletRef.current.isConnected) {
732
+ const addr = await walletRef.current.getAddress();
733
+ const cid = await walletRef.current.getChainId();
734
+ const userInfo = await walletRef.current.getUserInfo();
735
+ const whoamiInfo = await walletRef.current.getWhoami();
736
+ setAddress(addr);
737
+ setChainId(cid);
738
+ setUser(userInfo);
739
+ setWhoami(whoamiInfo);
740
+ setIsConnected(true);
741
+ }
742
+ }
743
+ catch (err) {
744
+ // If connect fails (user not authenticated), that's fine
745
+ // Just don't set connected state
746
+ if (walletRef.current && walletRef.current.isConnected) {
747
+ // Wallet says it's connected, restore state
748
+ try {
749
+ // Verify whoami exists before setting connected
750
+ const whoamiInfo = await walletRef.current.getWhoami();
751
+ if (!whoamiInfo) {
752
+ // If whoami is not available, don't set connected
753
+ return;
754
+ }
755
+ setIsConnected(true);
756
+ const userInfo = await walletRef.current.getUserInfo();
757
+ setUser(userInfo);
758
+ setWhoami(whoamiInfo);
759
+ // Try to get address, but don't fail if not available yet
760
+ try {
761
+ const addr = await walletRef.current.getAddress();
762
+ setAddress(addr);
763
+ }
764
+ catch (addrErr) {
765
+ console.warn('Address not available during restore:', addrErr);
766
+ }
767
+ const cid = await walletRef.current.getChainId();
768
+ setChainId(cid);
769
+ }
770
+ catch (restoreErr) {
771
+ console.error('Error restoring connection state:', restoreErr);
772
+ setIsConnected(false);
773
+ }
774
+ }
775
+ }
776
+ }
777
+ catch (err) {
778
+ const error = err instanceof Error ? err : new Error('Failed to initialize');
779
+ setError(error);
780
+ console.error('Init error:', err);
781
+ }
782
+ finally {
783
+ setLoading(false);
784
+ }
785
+ }, [isInitialized]);
786
+ // Connect wallet
787
+ const connect = useCallback(async () => {
788
+ if (!walletRef.current)
789
+ return;
790
+ setLoading(true);
791
+ setError(null);
792
+ try {
793
+ await walletRef.current.connect();
794
+ setIsConnected(walletRef.current.isConnected);
795
+ // Load whoami after connect
796
+ try {
797
+ const whoamiInfo = await walletRef.current.getWhoami();
798
+ setWhoami(whoamiInfo);
799
+ const addr = await walletRef.current.getAddress();
800
+ setAddress(addr);
801
+ }
802
+ catch (err) {
803
+ console.error('Error loading whoami after connect:', err);
804
+ }
805
+ }
806
+ catch (err) {
807
+ if (err instanceof AuthenticationError) {
808
+ // Show onboarding UI
809
+ showOnboarding();
810
+ setError(err);
811
+ }
812
+ else {
813
+ setError(err instanceof Error ? err : new Error('Failed to connect'));
814
+ }
815
+ }
816
+ finally {
817
+ setLoading(false);
818
+ }
819
+ }, [showOnboarding]);
820
+ // Disconnect wallet
821
+ const disconnect = useCallback(async () => {
822
+ setLoading(true);
823
+ setError(null);
824
+ disconnectingRef.current = true; // Set flag to prevent useEffect from interfering
825
+ try {
826
+ const wasExternalWalletConnected = isExternalWalletConnected;
827
+ const wasAbstraxnWalletConnected = walletRef.current?.isConnected ?? false;
828
+ // If external wallet is connected, disconnect it first
829
+ if (wasExternalWalletConnected && externalWalletsEnabled && wagmiDisconnect) {
830
+ try {
831
+ await wagmiDisconnect.disconnect();
832
+ // Wait a bit to ensure wagmi state updates
833
+ await new Promise(resolve => setTimeout(resolve, 150));
834
+ // Reset external wallet state immediately
835
+ setIsExternalWalletConnected(false);
836
+ setExternalWalletAddress(null);
837
+ setExternalWalletChainId(null);
838
+ explicitConnectionRef.current = false;
839
+ lastConnectionTimeRef.current = 0;
840
+ // Always reset main isConnected when disconnecting external wallet
841
+ // Since the context value is computed as isExternalWalletConnected || isConnected,
842
+ // we need to set isConnected to false so the overall isConnected becomes false
843
+ setIsConnected(false);
844
+ setAddress(null);
845
+ setChainId(null);
846
+ }
847
+ catch (err) {
848
+ throw err;
849
+ }
850
+ }
851
+ // Disconnect Abstraxn wallet if it's connected
852
+ if (wasAbstraxnWalletConnected && walletRef.current) {
853
+ // console.log('Disconnecting Abstraxn wallet...');
854
+ try {
855
+ await walletRef.current.disconnect();
856
+ // After disconnecting Abstraxn wallet, always reset main state
857
+ // The context value will be computed correctly based on both wallet states
858
+ setIsConnected(false);
859
+ setAddress(null);
860
+ setChainId(null);
861
+ // console.log('✅ Abstraxn wallet disconnected, main state reset');
862
+ }
863
+ catch (err) {
864
+ console.error('Error disconnecting Abstraxn wallet:', err);
865
+ // Continue even if Abstraxn wallet disconnect fails, but still reset state
866
+ setIsConnected(false);
867
+ setAddress(null);
868
+ setChainId(null);
869
+ }
870
+ }
871
+ // Final check: if neither wallet is connected, ensure main state is reset
872
+ // This is a safety check in case both were disconnected
873
+ const finalExternalConnected = isExternalWalletConnected;
874
+ const finalAbstraxnConnected = walletRef.current?.isConnected ?? false;
875
+ if (!finalExternalConnected && !finalAbstraxnConnected) {
876
+ setIsConnected(false);
877
+ setAddress(null);
878
+ setChainId(null);
879
+ // console.log('✅ Final check: All wallets disconnected, main state reset');
880
+ }
881
+ // Reset OTP screen and show initial login form
882
+ if (onboardingRef.current) {
883
+ const onboarding = onboardingRef.current;
884
+ onboarding.resetToLoginForm();
885
+ // If modal is visible (showing OTP screen), ensure it shows the login form
886
+ if (onboarding.modalOverlay && onboarding.modalOverlay.style.display !== 'none') {
887
+ // Modal is visible, just reset the form (already done above)
888
+ // Ensure modal is in open state
889
+ onboarding.modalOverlay.classList.remove('onboarding-modal-closing');
890
+ onboarding.modalOverlay.classList.add('onboarding-modal-open');
891
+ }
892
+ }
893
+ }
894
+ catch (err) {
895
+ setError(err instanceof Error ? err : new Error('Failed to disconnect'));
896
+ throw err;
897
+ }
898
+ finally {
899
+ // Clear disconnecting flag after a short delay to allow state to settle
900
+ setTimeout(() => {
901
+ disconnectingRef.current = false;
902
+ }, 200);
903
+ setLoading(false);
904
+ }
905
+ }, [isExternalWalletConnected, externalWalletsEnabled, wagmiDisconnect]);
906
+ // Hide onboarding UI
907
+ const hideOnboarding = useCallback(() => {
908
+ setError(null); // Clear any previous errors
909
+ if (onboardingRef.current) {
910
+ const onboarding = onboardingRef.current;
911
+ if (onboarding.modalOverlay) {
912
+ onboarding.modalOverlay.classList.remove('onboarding-modal-open');
913
+ onboarding.modalOverlay.classList.add('onboarding-modal-closing');
914
+ setTimeout(() => {
915
+ if (onboarding.modalOverlay) {
916
+ onboarding.modalOverlay.style.display = 'none';
917
+ }
918
+ }, 200);
919
+ }
920
+ document.body.style.overflow = '';
921
+ }
922
+ }, []);
923
+ // Get address - works for both Abstraxn wallet and external wallets
924
+ const getAddress = useCallback(async () => {
925
+ setError(null); // Clear any previous errors
926
+ // If external wallet is connected, return external wallet address
927
+ if (isExternalWalletConnected && externalWalletAddress) {
928
+ return externalWalletAddress;
929
+ }
930
+ // Otherwise use Abstraxn wallet
931
+ if (!walletRef.current)
932
+ throw new Error('Wallet not initialized');
933
+ return await walletRef.current.getAddress();
934
+ }, [isExternalWalletConnected, externalWalletAddress]);
935
+ // Get chain ID - works for both Abstraxn wallet and external wallets
936
+ const getChainId = useCallback(async () => {
937
+ setError(null); // Clear any previous errors
938
+ // If external wallet is connected, return external wallet chain ID
939
+ if (isExternalWalletConnected && externalWalletChainId) {
940
+ return externalWalletChainId;
941
+ }
942
+ // Otherwise use Abstraxn wallet
943
+ if (!walletRef.current)
944
+ throw new Error('Wallet not initialized');
945
+ return await walletRef.current.getChainId();
946
+ }, [isExternalWalletConnected, externalWalletChainId]);
947
+ // Switch chain - works for both Abstraxn wallet and external wallets
948
+ const switchChain = useCallback(async (newChainId) => {
949
+ setError(null);
950
+ // If external wallet is connected, use wagmi
951
+ if (isExternalWalletConnected && wagmiSwitchChain) {
952
+ try {
953
+ setLoading(true);
954
+ await wagmiSwitchChain.switchChainAsync({ chainId: newChainId });
955
+ }
956
+ catch (err) {
957
+ const error = err instanceof Error ? err : new Error('Failed to switch chain');
958
+ setError(error);
959
+ throw error;
960
+ }
961
+ finally {
962
+ setLoading(false);
963
+ }
964
+ }
965
+ else {
966
+ // Otherwise use Abstraxn wallet
967
+ if (!walletRef.current)
968
+ throw new Error('Wallet not initialized');
969
+ try {
970
+ await walletRef.current.switchChain(newChainId);
971
+ }
972
+ catch (err) {
973
+ setError(err instanceof Error ? err : new Error('Failed to switch chain'));
974
+ throw err;
975
+ }
976
+ }
977
+ }, [isExternalWalletConnected, wagmiSwitchChain]);
978
+ // Sign message - works for both Abstraxn wallet and external wallets
979
+ const signMessage = useCallback(async (message) => {
980
+ setError(null);
981
+ // If external wallet is connected, use wagmi
982
+ if (isExternalWalletConnected && externalWalletAddress && wagmiSignMessage) {
983
+ try {
984
+ setLoading(true);
985
+ const signature = await wagmiSignMessage.signMessageAsync({ message });
986
+ return signature;
987
+ }
988
+ catch (err) {
989
+ const error = err instanceof Error ? err : new Error('Failed to sign message');
990
+ setError(error);
991
+ throw error;
992
+ }
993
+ finally {
994
+ setLoading(false);
995
+ }
996
+ }
997
+ // Otherwise use Abstraxn wallet
998
+ if (!walletRef.current)
999
+ throw new Error('Wallet not initialized');
1000
+ try {
1001
+ return await walletRef.current.signMessage(message);
1002
+ }
1003
+ catch (err) {
1004
+ setError(err instanceof Error ? err : new Error('Failed to sign message'));
1005
+ throw err;
1006
+ }
1007
+ }, [isExternalWalletConnected, externalWalletAddress, wagmiSignMessage]);
1008
+ // Sign transaction
1009
+ const signTransaction = useCallback(async (tx) => {
1010
+ setError(null); // Clear any previous errors
1011
+ if (!walletRef.current)
1012
+ throw new Error('Wallet not initialized');
1013
+ try {
1014
+ return await walletRef.current.signTransaction(tx);
1015
+ }
1016
+ catch (err) {
1017
+ setError(err instanceof Error ? err : new Error('Failed to sign transaction'));
1018
+ throw err;
1019
+ }
1020
+ }, []);
1021
+ // Send transaction - works for both Abstraxn wallet and external wallets
1022
+ const sendTransaction = useCallback(async (tx) => {
1023
+ setError(null);
1024
+ // If external wallet is connected, use wagmi
1025
+ if (isExternalWalletConnected && externalWalletAddress && wagmiSendTransaction) {
1026
+ try {
1027
+ setLoading(true);
1028
+ // Convert TransactionRequest to wagmi format
1029
+ const wagmiTx = {
1030
+ to: tx.to,
1031
+ };
1032
+ if (tx.value) {
1033
+ wagmiTx.value = typeof tx.value === 'string' ? parseEther(tx.value) : BigInt(tx.value);
1034
+ }
1035
+ if (tx.data) {
1036
+ wagmiTx.data = tx.data;
1037
+ }
1038
+ if (tx.gasLimit) {
1039
+ wagmiTx.gas = tx.gasLimit;
1040
+ }
1041
+ if (tx.gasPrice) {
1042
+ wagmiTx.gasPrice = tx.gasPrice;
1043
+ }
1044
+ if (tx.maxFeePerGas) {
1045
+ wagmiTx.maxFeePerGas = tx.maxFeePerGas;
1046
+ }
1047
+ if (tx.maxPriorityFeePerGas) {
1048
+ wagmiTx.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;
1049
+ }
1050
+ const hash = await wagmiSendTransaction.sendTransactionAsync(wagmiTx);
1051
+ return {
1052
+ hash: hash,
1053
+ };
1054
+ }
1055
+ catch (err) {
1056
+ const error = err instanceof Error ? err : new Error('Failed to send transaction');
1057
+ setError(error);
1058
+ throw error;
1059
+ }
1060
+ finally {
1061
+ setLoading(false);
1062
+ }
1063
+ }
1064
+ // Otherwise use Abstraxn wallet
1065
+ if (!walletRef.current)
1066
+ throw new Error('Wallet not initialized');
1067
+ try {
1068
+ return await walletRef.current.sendTransaction(tx);
1069
+ }
1070
+ catch (err) {
1071
+ setError(err instanceof Error ? err : new Error('Failed to send transaction'));
1072
+ throw err;
1073
+ }
1074
+ }, [isExternalWalletConnected, externalWalletAddress, wagmiSendTransaction]);
1075
+ // Sign transaction via API (returns signed transaction)
1076
+ const signTransactionViaAPI = useCallback(async (unsignedTransaction, fromAddress) => {
1077
+ if (!walletRef.current)
1078
+ throw new Error('Wallet not initialized');
1079
+ setLoading(true);
1080
+ setError(null);
1081
+ try {
1082
+ return await walletRef.current.signTransactionViaAPI(unsignedTransaction, fromAddress);
1083
+ }
1084
+ catch (err) {
1085
+ const error = err instanceof Error ? err : new Error('Failed to sign transaction');
1086
+ setError(error);
1087
+ throw error;
1088
+ }
1089
+ finally {
1090
+ setLoading(false);
1091
+ }
1092
+ }, []);
1093
+ // Sign and send transaction using backend API
1094
+ const signAndSendTransaction = useCallback(async (unsignedTransaction, fromAddress, rpcUrl) => {
1095
+ if (!walletRef.current)
1096
+ throw new Error('Wallet not initialized');
1097
+ setLoading(true);
1098
+ setError(null);
1099
+ try {
1100
+ return await walletRef.current.signAndSendTransaction(unsignedTransaction, fromAddress, rpcUrl);
1101
+ }
1102
+ catch (err) {
1103
+ const error = err instanceof Error ? err : new Error('Failed to sign and send transaction');
1104
+ setError(error);
1105
+ throw error;
1106
+ }
1107
+ finally {
1108
+ setLoading(false);
1109
+ }
1110
+ }, []);
1111
+ // Login with email OTP (initiate OTP)
1112
+ const loginWithOTP = useCallback(async (email) => {
1113
+ if (!walletRef.current)
1114
+ throw new Error('Wallet not initialized');
1115
+ setLoading(true);
1116
+ setError(null);
1117
+ try {
1118
+ const result = await walletRef.current.loginWithOTP(email);
1119
+ return result;
1120
+ }
1121
+ catch (err) {
1122
+ const error = err instanceof Error ? err : new Error('Failed to initiate OTP');
1123
+ setError(error);
1124
+ throw error;
1125
+ }
1126
+ finally {
1127
+ setLoading(false);
1128
+ }
1129
+ }, []);
1130
+ // Verify OTP
1131
+ const verifyOTP = useCallback(async (otpId, otpCode) => {
1132
+ if (!walletRef.current)
1133
+ throw new Error('Wallet not initialized');
1134
+ setLoading(true);
1135
+ setError(null);
1136
+ try {
1137
+ const user = await walletRef.current.verifyOTP(otpId, otpCode);
1138
+ setIsConnected(true);
1139
+ setUser(user);
1140
+ // Load whoami and address after verification
1141
+ try {
1142
+ const whoamiInfo = await walletRef.current.getWhoami();
1143
+ setWhoami(whoamiInfo);
1144
+ const addr = await walletRef.current.getAddress();
1145
+ setAddress(addr);
1146
+ const cid = await walletRef.current.getChainId();
1147
+ setChainId(cid);
1148
+ }
1149
+ catch (err) {
1150
+ console.error('Error loading whoami after OTP verification:', err);
1151
+ }
1152
+ }
1153
+ catch (err) {
1154
+ const error = err instanceof Error ? err : new Error('Failed to verify OTP');
1155
+ setError(error);
1156
+ throw error;
1157
+ }
1158
+ finally {
1159
+ setLoading(false);
1160
+ }
1161
+ }, []);
1162
+ // Login with Google
1163
+ const loginWithGoogle = useCallback(async () => {
1164
+ if (!walletRef.current)
1165
+ throw new Error('Wallet not initialized');
1166
+ setLoading(true);
1167
+ setError(null);
1168
+ try {
1169
+ await walletRef.current.loginWithGoogle();
1170
+ // Note: This will redirect, so loading state will be reset on callback
1171
+ }
1172
+ catch (err) {
1173
+ const error = err instanceof Error ? err : new Error('Failed to initiate Google login');
1174
+ setError(error);
1175
+ setLoading(false);
1176
+ throw error;
1177
+ }
1178
+ }, []);
1179
+ // Handle Google callback
1180
+ const handleGoogleCallback = useCallback(async () => {
1181
+ if (!walletRef.current)
1182
+ return;
1183
+ setLoading(true);
1184
+ setError(null);
1185
+ try {
1186
+ // handleGoogleCallback() will initialize storage internally
1187
+ // Don't call connect() here as it checks for authentication which isn't available yet
1188
+ const user = await walletRef.current.handleGoogleCallback();
1189
+ if (user) {
1190
+ setUser(user);
1191
+ // handleGoogleCallback() already calls whoami and emits 'connect' event
1192
+ // The 'connect' event handler will update whoami, address, and chainId
1193
+ // So we don't need to do it here to avoid duplicate whoami calls
1194
+ }
1195
+ }
1196
+ catch (err) {
1197
+ const error = err instanceof Error ? err : new Error('Failed to handle Google callback');
1198
+ setError(error);
1199
+ throw error;
1200
+ }
1201
+ finally {
1202
+ setLoading(false);
1203
+ }
1204
+ }, []);
1205
+ // Refresh whoami - makes a fresh API call and updates state
1206
+ // For external wallets, whoami is not applicable (no API backend)
1207
+ const refreshWhoami = useCallback(async () => {
1208
+ // If external wallet is connected, return null (whoami is only for Abstraxn wallet)
1209
+ if (isExternalWalletConnected) {
1210
+ return null;
1211
+ }
1212
+ if (!walletRef.current) {
1213
+ return null;
1214
+ }
1215
+ // Check if Abstraxn wallet is actually connected before calling refreshWhoami
1216
+ if (!walletRef.current.isConnected) {
1217
+ return null;
1218
+ }
1219
+ setLoading(true);
1220
+ setError(null);
1221
+ try {
1222
+ const whoamiInfo = await walletRef.current.refreshWhoami();
1223
+ if (whoamiInfo) {
1224
+ setWhoami(whoamiInfo);
1225
+ // Update address if available in whoami response
1226
+ if (whoamiInfo.address) {
1227
+ setAddress(whoamiInfo.address);
1228
+ }
1229
+ }
1230
+ return whoamiInfo;
1231
+ }
1232
+ catch (err) {
1233
+ const error = err instanceof Error ? err : new Error('Failed to refresh whoami');
1234
+ setError(error);
1235
+ throw error;
1236
+ }
1237
+ finally {
1238
+ setLoading(false);
1239
+ }
1240
+ }, [isExternalWalletConnected]);
1241
+ // Sync external wallet state from wagmi
1242
+ useEffect(() => {
1243
+ let checkAddressTimeout = null;
1244
+ // Prevent infinite loops - if we're already updating, skip
1245
+ if (isUpdatingRef.current) {
1246
+ return;
1247
+ }
1248
+ if (!externalWalletsEnabled || !wagmiAccount) {
1249
+ return;
1250
+ }
1251
+ // Prevent auto-connect
1252
+ if (wagmiAccount.isConnected && !explicitConnectionRef.current && !autoDisconnectHandledRef.current) {
1253
+ autoDisconnectHandledRef.current = true;
1254
+ if (wagmiDisconnect) {
1255
+ setTimeout(() => {
1256
+ wagmiDisconnect.disconnect();
1257
+ }, 0);
1258
+ }
1259
+ return;
1260
+ }
1261
+ // If wagmiAccount is not connected and we have explicitConnectionRef set to false,
1262
+ // it means we explicitly disconnected, so don't try to reconnect
1263
+ if (!wagmiAccount.isConnected) {
1264
+ autoDisconnectHandledRef.current = false;
1265
+ // If we're in the middle of disconnecting, don't interfere
1266
+ if (disconnectingRef.current) {
1267
+ return;
1268
+ }
1269
+ // If we explicitly disconnected (explicitConnectionRef is false and we were connected),
1270
+ // ensure state is reset immediately
1271
+ if (!explicitConnectionRef.current && isExternalWalletConnected) {
1272
+ isUpdatingRef.current = true;
1273
+ setIsExternalWalletConnected(false);
1274
+ setExternalWalletAddress(null);
1275
+ setExternalWalletChainId(null);
1276
+ lastAddressRef.current = null;
1277
+ lastChainIdRef.current = null;
1278
+ // Only reset main state if Abstraxn wallet is also not connected
1279
+ if (!walletRef.current?.isConnected) {
1280
+ setIsConnected(false);
1281
+ setAddress(null);
1282
+ setChainId(null);
1283
+ }
1284
+ lastConnectionTimeRef.current = 0;
1285
+ isUpdatingRef.current = false;
1286
+ return;
1287
+ }
1288
+ // If we're not connected but explicitConnectionRef is true, it means we're trying to reconnect
1289
+ // Don't reset state in this case - wait for the connection to complete
1290
+ if (!wagmiAccount.isConnected && explicitConnectionRef.current) {
1291
+ // Connection is in progress, don't interfere
1292
+ return;
1293
+ }
1294
+ }
1295
+ // Check if external wallet is connected
1296
+ if (wagmiAccount.isConnected && wagmiAccount.address) {
1297
+ const walletAddress = wagmiAccount.address;
1298
+ // Ensure address is properly formatted (should be 0x...)
1299
+ const formattedAddress = walletAddress && typeof walletAddress === 'string'
1300
+ ? walletAddress.toLowerCase()
1301
+ : walletAddress;
1302
+ // Use wagmiChainIdHook first (more reliable), then fallback to wagmiAccount.chainId
1303
+ const currentChainId = wagmiChainIdHook || wagmiAccount.chainId || null;
1304
+ // Check if address or chainId actually changed to prevent unnecessary updates
1305
+ const addressChanged = lastAddressRef.current !== formattedAddress;
1306
+ const chainIdChanged = lastChainIdRef.current !== currentChainId;
1307
+ // Also check if we need to reconnect (lastAddressRef is null but wagmiAccount is connected)
1308
+ // This handles the case where user disconnects and then reconnects
1309
+ const needsReconnect = lastAddressRef.current === null && wagmiAccount.isConnected && formattedAddress;
1310
+ // Only update if something actually changed OR if we need to reconnect
1311
+ if (addressChanged || chainIdChanged || needsReconnect) {
1312
+ isUpdatingRef.current = true;
1313
+ // Update address if it changed or if we need to reconnect
1314
+ if (addressChanged || needsReconnect) {
1315
+ setIsExternalWalletConnected(true);
1316
+ setExternalWalletAddress(formattedAddress);
1317
+ setIsConnected(true);
1318
+ setAddress(formattedAddress);
1319
+ lastAddressRef.current = formattedAddress;
1320
+ lastConnectionTimeRef.current = Date.now();
1321
+ }
1322
+ // Update chainId if it changed or if we need to reconnect
1323
+ if ((chainIdChanged && currentChainId) || (needsReconnect && currentChainId)) {
1324
+ setExternalWalletChainId(currentChainId);
1325
+ setChainId(currentChainId);
1326
+ lastChainIdRef.current = currentChainId;
1327
+ }
1328
+ isUpdatingRef.current = false;
1329
+ // Close the onboarding modal after successful external wallet connection (only once)
1330
+ if (addressChanged && onboardingRef.current) {
1331
+ const onboardingAny = onboardingRef.current;
1332
+ if (onboardingAny.modalOverlay) {
1333
+ setTimeout(() => {
1334
+ if (onboardingAny.modalOverlay) {
1335
+ onboardingAny.modalOverlay.classList.remove('onboarding-modal-open');
1336
+ onboardingAny.modalOverlay.classList.add('onboarding-modal-closing');
1337
+ setTimeout(() => {
1338
+ if (onboardingAny.modalOverlay) {
1339
+ onboardingAny.modalOverlay.style.display = 'none';
1340
+ }
1341
+ }, 200);
1342
+ document.body.style.overflow = '';
1343
+ }
1344
+ }, 100);
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ else if (!wagmiAccount.isConnected) {
1350
+ // Only reset state if:
1351
+ // 1. We have a stored address (meaning we were connected), AND
1352
+ // 2. It's been more than 1 second since last connection (to avoid premature reset during re-renders), AND
1353
+ // 3. This is not an auto-disconnect scenario
1354
+ const timeSinceLastConnection = Date.now() - lastConnectionTimeRef.current;
1355
+ const shouldReset = lastAddressRef.current !== null &&
1356
+ timeSinceLastConnection > 1000 &&
1357
+ !autoDisconnectHandledRef.current;
1358
+ if (shouldReset) {
1359
+ isUpdatingRef.current = true;
1360
+ setIsExternalWalletConnected(false);
1361
+ setExternalWalletAddress(null);
1362
+ setExternalWalletChainId(null);
1363
+ lastAddressRef.current = null;
1364
+ lastChainIdRef.current = null;
1365
+ // Only reset if Abstraxn wallet is also not connected
1366
+ if (!walletRef.current?.isConnected) {
1367
+ setIsConnected(false);
1368
+ setAddress(null);
1369
+ setChainId(null);
1370
+ }
1371
+ explicitConnectionRef.current = false;
1372
+ lastConnectionTimeRef.current = 0;
1373
+ isUpdatingRef.current = false;
1374
+ }
1375
+ }
1376
+ // Cleanup function
1377
+ return () => {
1378
+ if (checkAddressTimeout) {
1379
+ clearTimeout(checkAddressTimeout);
1380
+ }
1381
+ };
1382
+ }, [wagmiAccount?.isConnected, wagmiAccount?.address, wagmiAccount?.chainId, wagmiChainIdHook, externalWalletsEnabled, wagmiDisconnect]);
1383
+ // Connect external wallet
1384
+ const connectExternalWallet = useCallback(async (connectorId) => {
1385
+ if (!externalWalletsEnabled || !wagmiConnect || !wagmiDisconnect) {
1386
+ const error = new Error('External wallets are not enabled or wagmi is not initialized');
1387
+ throw error;
1388
+ }
1389
+ setLoading(true);
1390
+ setError(null);
1391
+ try {
1392
+ if (wagmiAccount?.isConnected) {
1393
+ try {
1394
+ await wagmiDisconnect.disconnect();
1395
+ await new Promise(resolve => setTimeout(resolve, 100));
1396
+ }
1397
+ catch (disconnectErr) {
1398
+ // Ignore
1399
+ }
1400
+ }
1401
+ // Try to find connector by exact ID first
1402
+ let connector = wagmiConnect.connectors.find((c) => c.id === connectorId);
1403
+ // If not found, try to find by matching patterns for specific wallets
1404
+ if (!connector) {
1405
+ const connectorIdLower = connectorId.toLowerCase();
1406
+ // Check for MetaMask - prioritize io.metamask over injected
1407
+ if (connectorIdLower.includes('metamask') || connectorIdLower.includes('io.metamask')) {
1408
+ // First, try to find io.metamask specifically
1409
+ connector = wagmiConnect.connectors.find((c) => {
1410
+ const id = c.id.toLowerCase();
1411
+ return id === 'io.metamask' || id.includes('io.metamask');
1412
+ });
1413
+ // If io.metamask not found, fall back to injected (but only if MetaMask is requested)
1414
+ if (!connector) {
1415
+ connector = wagmiConnect.connectors.find((c) => {
1416
+ const id = c.id.toLowerCase();
1417
+ const name = c.name?.toLowerCase() || '';
1418
+ return (id.includes('injected') || id.includes('metamask')) &&
1419
+ (name.includes('metamask') || (typeof window !== 'undefined' && window.ethereum?.isMetaMask));
1420
+ });
1421
+ }
1422
+ }
1423
+ // Check for generic injected (only if not MetaMask)
1424
+ else if (connectorIdLower.includes('injected')) {
1425
+ connector = wagmiConnect.connectors.find((c) => {
1426
+ const id = c.id.toLowerCase();
1427
+ // Exclude io.metamask and other specific connectors
1428
+ return id === 'injected' && !id.includes('io.metamask') && !id.includes('com.coinbase') && !id.includes('app.phantom');
1429
+ });
1430
+ }
1431
+ // Check for Coinbase Wallet
1432
+ else if (connectorIdLower.includes('coinbase') || connectorIdLower.includes('com.coinbase.wallet')) {
1433
+ connector = wagmiConnect.connectors.find((c) => {
1434
+ const id = c.id.toLowerCase();
1435
+ const name = c.name?.toLowerCase() || '';
1436
+ return id.includes('coinbase') || id.includes('com.coinbase') ||
1437
+ name.includes('coinbase');
1438
+ });
1439
+ }
1440
+ // Check for Phantom
1441
+ else if (connectorIdLower.includes('phantom') || connectorIdLower.includes('app.phantom')) {
1442
+ connector = wagmiConnect.connectors.find((c) => {
1443
+ const id = c.id.toLowerCase();
1444
+ const name = c.name?.toLowerCase() || '';
1445
+ return id.includes('phantom') || id.includes('app.phantom') ||
1446
+ name.includes('phantom');
1447
+ });
1448
+ }
1449
+ // Check for WalletConnect
1450
+ else if (connectorIdLower.includes('walletconnect') || connectorIdLower.includes('wallet_connect')) {
1451
+ connector = wagmiConnect.connectors.find((c) => {
1452
+ const id = c.id.toLowerCase();
1453
+ const name = c.name?.toLowerCase() || '';
1454
+ return id.includes('walletconnect') || id.includes('wallet_connect') ||
1455
+ name.includes('walletconnect') || name.includes('wallet connect');
1456
+ });
1457
+ }
1458
+ // Try to find any connector that matches (fallback)
1459
+ else {
1460
+ connector = wagmiConnect.connectors.find((c) => {
1461
+ const id = c.id.toLowerCase();
1462
+ return id === connectorIdLower || id.includes(connectorIdLower);
1463
+ });
1464
+ }
1465
+ }
1466
+ if (!connector) {
1467
+ const availableIds = wagmiConnect.connectors.map((c) => c.id).join(', ');
1468
+ const error = new Error(`Connector "${connectorId}" not found. Available connectors: ${availableIds}`);
1469
+ throw error;
1470
+ }
1471
+ autoDisconnectHandledRef.current = false;
1472
+ explicitConnectionRef.current = true;
1473
+ // CRITICAL: Reset refs before connecting to ensure reconnect is detected
1474
+ // This ensures that when we reconnect after a disconnect, the address change is detected
1475
+ lastAddressRef.current = null;
1476
+ lastChainIdRef.current = null;
1477
+ try {
1478
+ await wagmiConnect.connect({ connector });
1479
+ }
1480
+ catch (connectError) {
1481
+ explicitConnectionRef.current = false;
1482
+ throw connectError;
1483
+ }
1484
+ // Wait a bit for wagmiAccount to update with address
1485
+ // Sometimes the address is not immediately available after connection
1486
+ await new Promise(resolve => setTimeout(resolve, 300));
1487
+ // Force a check and set state directly - this ensures address is set even if useEffect hasn't run yet
1488
+ // This is the PRIMARY way we set the address after connection
1489
+ // IMPORTANT: For ALL external wallets (MetaMask, Coinbase, Phantom, WalletConnect, etc.),
1490
+ // we MUST set isExternalWalletConnected to true
1491
+ if (wagmiAccount?.address) {
1492
+ const formattedAddress = wagmiAccount.address.toLowerCase();
1493
+ const currentChainId = wagmiAccount.chainId || null;
1494
+ // Set state directly to ensure it's updated immediately
1495
+ // This happens AFTER explicitConnectionRef is set to true, so it should always work
1496
+ // CRITICAL: Set isExternalWalletConnected to true FIRST for ALL external wallet connections
1497
+ // This ensures the connection is recognized as an external wallet, not Abstraxn wallet
1498
+ setExternalWalletAddress(formattedAddress);
1499
+ setIsExternalWalletConnected(true); // This marks it as an external wallet connection
1500
+ setAddress(formattedAddress);
1501
+ setIsConnected(true);
1502
+ if (currentChainId) {
1503
+ setChainId(currentChainId);
1504
+ setExternalWalletChainId(currentChainId);
1505
+ }
1506
+ // Track connection time and update refs to prevent premature reset
1507
+ lastConnectionTimeRef.current = Date.now();
1508
+ lastAddressRef.current = formattedAddress;
1509
+ lastChainIdRef.current = currentChainId;
1510
+ // Close the onboarding modal
1511
+ if (onboardingRef.current) {
1512
+ const onboardingAny = onboardingRef.current;
1513
+ if (onboardingAny.modalOverlay) {
1514
+ setTimeout(() => {
1515
+ if (onboardingAny.modalOverlay) {
1516
+ onboardingAny.modalOverlay.classList.remove('onboarding-modal-open');
1517
+ onboardingAny.modalOverlay.classList.add('onboarding-modal-closing');
1518
+ setTimeout(() => {
1519
+ if (onboardingAny.modalOverlay) {
1520
+ onboardingAny.modalOverlay.style.display = 'none';
1521
+ }
1522
+ }, 200);
1523
+ document.body.style.overflow = '';
1524
+ }
1525
+ }, 100);
1526
+ }
1527
+ }
1528
+ }
1529
+ }
1530
+ catch (err) {
1531
+ explicitConnectionRef.current = false;
1532
+ const error = err instanceof Error ? err : new Error(`Failed to connect external wallet: ${err instanceof Error ? err.message : String(err)}`);
1533
+ setError(error);
1534
+ throw error;
1535
+ }
1536
+ finally {
1537
+ setLoading(false);
1538
+ }
1539
+ }, [externalWalletsEnabled, wagmiConnect, wagmiDisconnect, wagmiAccount?.isConnected]);
1540
+ // Disconnect external wallet
1541
+ const disconnectExternalWallet = useCallback(async () => {
1542
+ if (!externalWalletsEnabled || !wagmiDisconnect) {
1543
+ return;
1544
+ }
1545
+ setLoading(true);
1546
+ setError(null);
1547
+ disconnectingRef.current = true; // Set flag to prevent useEffect from interfering
1548
+ try {
1549
+ const wasAbstraxnConnected = walletRef.current?.isConnected ?? false;
1550
+ // Await the disconnect to ensure it completes
1551
+ await wagmiDisconnect.disconnect();
1552
+ // Wait a bit to ensure wagmi state updates
1553
+ await new Promise(resolve => setTimeout(resolve, 150));
1554
+ // Reset external wallet state immediately
1555
+ setIsExternalWalletConnected(false);
1556
+ setExternalWalletAddress(null);
1557
+ setExternalWalletChainId(null);
1558
+ explicitConnectionRef.current = false;
1559
+ lastConnectionTimeRef.current = 0;
1560
+ // CRITICAL: Reset refs so reconnect is detected as a new connection
1561
+ lastAddressRef.current = null;
1562
+ lastChainIdRef.current = null;
1563
+ // Always reset main state when disconnecting external wallet
1564
+ // The context value is computed as isExternalWalletConnected || isConnected,
1565
+ // so setting both to false ensures the overall isConnected becomes false
1566
+ setIsConnected(false);
1567
+ setAddress(null);
1568
+ setChainId(null);
1569
+ }
1570
+ catch (err) {
1571
+ const error = err instanceof Error ? err : new Error('Failed to disconnect external wallet');
1572
+ console.error('Error disconnecting external wallet:', error);
1573
+ setError(error);
1574
+ throw error;
1575
+ }
1576
+ finally {
1577
+ // Clear disconnecting flag after a short delay to allow state to settle
1578
+ setTimeout(() => {
1579
+ disconnectingRef.current = false;
1580
+ }, 200);
1581
+ setLoading(false);
1582
+ }
1583
+ }, [externalWalletsEnabled, wagmiDisconnect]);
1584
+ // Helper function to get network name from chain ID
1585
+ const getNetworkName = useCallback((chainId) => {
1586
+ const chainNames = {
1587
+ 1: 'Ethereum Mainnet',
1588
+ 5: 'Goerli',
1589
+ 11155111: 'Sepolia',
1590
+ 137: 'Polygon',
1591
+ 80001: 'Mumbai',
1592
+ 80002: 'Polygon Amoy',
1593
+ 8453: 'Base',
1594
+ 84531: 'Base Goerli',
1595
+ 42161: 'Arbitrum One',
1596
+ 421613: 'Arbitrum Goerli',
1597
+ 10: 'Optimism',
1598
+ 420: 'Optimism Goerli',
1599
+ 56: 'BNB Smart Chain',
1600
+ 97: 'BNB Smart Chain Testnet',
1601
+ };
1602
+ return chainNames[chainId] || `Chain ${chainId}`;
1603
+ }, []);
1604
+ // Switch external wallet chain (dedicated function for external wallets)
1605
+ const switchExternalWalletChain = useCallback(async (chainId) => {
1606
+ if (!externalWalletsEnabled || !wagmiSwitchChain) {
1607
+ const error = new Error('External wallets are not enabled or chain switching is not available');
1608
+ setError(error);
1609
+ throw error;
1610
+ }
1611
+ setError(null);
1612
+ setLoading(true);
1613
+ try {
1614
+ // Use switchChainAsync for wagmi v3
1615
+ await wagmiSwitchChain.switchChainAsync({ chainId });
1616
+ // Update local state - wagmi will update chainId via useChainId hook
1617
+ // But we also update our local state for immediate feedback
1618
+ setExternalWalletChainId(chainId);
1619
+ }
1620
+ catch (err) {
1621
+ const error = err instanceof Error ? err : new Error(`Failed to switch chain: ${err instanceof Error ? err.message : String(err)}`);
1622
+ setError(error);
1623
+ throw error;
1624
+ }
1625
+ finally {
1626
+ setLoading(false);
1627
+ }
1628
+ }, [externalWalletsEnabled, wagmiSwitchChain]);
1629
+ // Get available connectors - return all connectors with their IDs and names
1630
+ // Filter out generic 'injected' connector if 'io.metamask' is available (prefer specific connectors)
1631
+ // The ExternalWalletButtons component will handle additional deduplication and display
1632
+ const availableConnectors = externalWalletsEnabled && wagmiConnect && wagmiConnect.connectors
1633
+ ? wagmiConnect.connectors
1634
+ .filter((connector) => {
1635
+ // If io.metamask exists, exclude generic 'injected' connector
1636
+ const hasIoMetaMask = wagmiConnect.connectors.some((c) => c.id === 'io.metamask');
1637
+ if (hasIoMetaMask && connector.id === 'injected') {
1638
+ return false; // Exclude generic injected when io.metamask is available
1639
+ }
1640
+ return true;
1641
+ })
1642
+ .map((connector) => ({
1643
+ id: connector.id,
1644
+ name: connector.name,
1645
+ icon: connector.icon, // Pass the icon from the connector
1646
+ }))
1647
+ : [];
1648
+ // Determine the current address (external wallet takes precedence)
1649
+ const currentAddress = isExternalWalletConnected && externalWalletAddress
1650
+ ? externalWalletAddress
1651
+ : address;
1652
+ // Determine the current chain ID (external wallet takes precedence)
1653
+ const currentChainId = isExternalWalletConnected && externalWalletChainId
1654
+ ? externalWalletChainId
1655
+ : chainId;
1656
+ const value = {
1657
+ wallet: walletRef.current,
1658
+ isInitialized,
1659
+ isConnected: isExternalWalletConnected || isConnected, // True if either wallet is connected
1660
+ address: currentAddress, // Use external wallet address if connected, otherwise Abstraxn wallet address
1661
+ user,
1662
+ whoami,
1663
+ chainId: currentChainId, // Use external wallet chain ID if connected, otherwise Abstraxn wallet chain ID
1664
+ error,
1665
+ loading,
1666
+ init,
1667
+ connect,
1668
+ disconnect,
1669
+ showOnboarding,
1670
+ hideOnboarding,
1671
+ getAddress,
1672
+ getChainId,
1673
+ switchChain,
1674
+ signMessage,
1675
+ signTransaction,
1676
+ sendTransaction,
1677
+ signTransactionViaAPI,
1678
+ signAndSendTransaction,
1679
+ loginWithOTP,
1680
+ verifyOTP,
1681
+ loginWithGoogle,
1682
+ handleGoogleCallback,
1683
+ refreshWhoami,
1684
+ uiConfig: config.ui,
1685
+ // External wallet methods
1686
+ connectExternalWallet: externalWalletsEnabled ? connectExternalWallet : undefined,
1687
+ disconnectExternalWallet: externalWalletsEnabled ? disconnectExternalWallet : undefined,
1688
+ isExternalWalletConnected: externalWalletsEnabled ? isExternalWalletConnected : undefined,
1689
+ externalWalletAddress: externalWalletsEnabled ? externalWalletAddress : undefined,
1690
+ externalWalletChainId: externalWalletsEnabled ? externalWalletChainId : undefined,
1691
+ externalWalletBalance: externalWalletsEnabled && wagmiBalance?.data && typeof wagmiBalance.data === 'object' && wagmiBalance.data !== null && 'value' in wagmiBalance.data ? wagmiBalance.data.value : undefined,
1692
+ externalWalletNetwork: externalWalletsEnabled && wagmiChainIdHook ? getNetworkName(wagmiChainIdHook) : undefined,
1693
+ availableConnectors: externalWalletsEnabled ? availableConnectors : undefined,
1694
+ switchExternalWalletChain: externalWalletsEnabled ? switchExternalWalletChain : undefined,
1695
+ };
1696
+ // Ref to store latest value to avoid dependency issues (defined after value)
1697
+ const valueRef = useRef(value);
1698
+ valueRef.current = value;
1699
+ // Debug log when address changes
1700
+ // useEffect(() => {
1701
+ // console.log('🔍 Context value check:', {
1702
+ // currentAddress,
1703
+ // isConnected: isExternalWalletConnected || isConnected,
1704
+ // isExternalWalletConnected,
1705
+ // externalWalletAddress,
1706
+ // address,
1707
+ // explicitConnection: explicitConnectionRef.current,
1708
+ // });
1709
+ // }, [currentAddress, isExternalWalletConnected, isConnected, externalWalletAddress, address]);
1710
+ // Reusable function to mount external wallets (moved here so it can be used by showOnboarding)
1711
+ const mountExternalWalletsToContainer = useCallback(async (container, onboardingAny) => {
1712
+ if (!container) {
1713
+ return;
1714
+ }
1715
+ // Check if we're on OTP screen - if so, don't show external wallets
1716
+ const isOtpScreen = onboardingAny.otpVerificationScreen &&
1717
+ onboardingAny.otpVerificationScreen.parentElement &&
1718
+ onboardingAny.otpVerificationScreen.offsetParent !== null;
1719
+ if (!isOtpScreen) {
1720
+ // Only show external wallets if not on OTP screen
1721
+ container.style.display = '';
1722
+ // Show divider only if both email/Google AND external wallets are visible
1723
+ const authMethods = onboardingAny.config?.authMethods || ['otp', 'google'];
1724
+ const showEmail = authMethods.includes('otp');
1725
+ const showGoogle = authMethods.includes('google');
1726
+ const hasEmailOrGoogle = showEmail || showGoogle;
1727
+ if (onboardingAny.externalWalletDivider && hasEmailOrGoogle) {
1728
+ onboardingAny.externalWalletDivider.style.display = '';
1729
+ }
1730
+ else if (onboardingAny.externalWalletDivider) {
1731
+ onboardingAny.externalWalletDivider.style.display = 'none';
1732
+ }
1733
+ }
1734
+ else {
1735
+ // On OTP screen - hide external wallets
1736
+ container.style.display = 'none';
1737
+ if (onboardingAny.externalWalletDivider) {
1738
+ onboardingAny.externalWalletDivider.style.display = 'none';
1739
+ }
1740
+ return;
1741
+ }
1742
+ // Check if root exists and container is still in DOM
1743
+ const containerInDOM = container.parentElement !== null;
1744
+ // Check if the container is the same as the one we created the root for
1745
+ const isSameContainer = externalWalletContainerRef.current === container;
1746
+ const rootStillValid = externalWalletRootRef.current && containerInDOM && isSameContainer;
1747
+ const renderExternalWalletButtons = () => (_jsx(AbstraxnContext.Provider, { value: valueRef.current, children: _jsx(ExternalWalletButtons, { onConnect: async () => {
1748
+ }, onShowMoreWallets: () => {
1749
+ const onboardingAny = onboardingRef.current;
1750
+ if (onboardingAny?.emailForm) {
1751
+ onboardingAny.emailForm.style.display = 'none';
1752
+ }
1753
+ if (onboardingAny?.googleButton) {
1754
+ onboardingAny.googleButton.style.display = 'none';
1755
+ }
1756
+ // Hide all passkey elements when wallet selection screen is shown
1757
+ if (onboardingAny?.passkeyLoginButton) {
1758
+ onboardingAny.passkeyLoginButton.style.display = 'none';
1759
+ }
1760
+ if (onboardingAny?.passkeySignupLink) {
1761
+ onboardingAny.passkeySignupLink.style.display = 'none';
1762
+ }
1763
+ if (onboardingAny?.passkeyErrorElement) {
1764
+ onboardingAny.passkeyErrorElement.style.display = 'none';
1765
+ }
1766
+ if (onboardingAny?.passkeyDivider) {
1767
+ onboardingAny.passkeyDivider.style.display = 'none';
1768
+ }
1769
+ if (onboardingAny?.externalWalletDivider) {
1770
+ onboardingAny.externalWalletDivider.style.display = 'none';
1771
+ }
1772
+ if (onboardingAny?.divider) {
1773
+ onboardingAny.divider.style.display = 'none';
1774
+ }
1775
+ }, onHideMoreWallets: () => {
1776
+ const onboardingAny = onboardingRef.current;
1777
+ const authMethods = onboardingAny?.config?.authMethods || ['otp', 'google'];
1778
+ const showEmail = authMethods.includes('otp');
1779
+ const showGoogle = authMethods.includes('google');
1780
+ const showPasskey = authMethods.includes('passkey');
1781
+ if (onboardingAny?.emailForm) {
1782
+ onboardingAny.emailForm.style.display = showEmail ? '' : 'none';
1783
+ }
1784
+ if (onboardingAny?.googleButton) {
1785
+ onboardingAny.googleButton.style.display = showGoogle ? '' : 'none';
1786
+ }
1787
+ // Show passkey controls if enabled
1788
+ if (onboardingAny?.passkeyLoginButton) {
1789
+ onboardingAny.passkeyLoginButton.style.display = showPasskey ? '' : 'none';
1790
+ }
1791
+ if (onboardingAny?.passkeySignupLink) {
1792
+ onboardingAny.passkeySignupLink.style.display = showPasskey ? '' : 'none';
1793
+ }
1794
+ if (onboardingAny?.passkeyErrorElement) {
1795
+ onboardingAny.passkeyErrorElement.style.display = 'none';
1796
+ }
1797
+ // Show passkey divider if passkey is enabled and there are other auth methods
1798
+ if (onboardingAny?.passkeyDivider) {
1799
+ onboardingAny.passkeyDivider.style.display = (showPasskey && (showEmail || showGoogle)) ? '' : 'none';
1800
+ }
1801
+ if (onboardingAny?.divider) {
1802
+ onboardingAny.divider.style.display = (showEmail && showGoogle) ? '' : 'none';
1803
+ }
1804
+ const hasEmailOrGoogle = showEmail || showGoogle;
1805
+ if (onboardingAny?.externalWalletDivider && hasEmailOrGoogle) {
1806
+ onboardingAny.externalWalletDivider.style.display = '';
1807
+ }
1808
+ else if (onboardingAny?.externalWalletDivider) {
1809
+ onboardingAny.externalWalletDivider.style.display = 'none';
1810
+ }
1811
+ } }) }));
1812
+ if (rootStillValid) {
1813
+ externalWalletRootRef.current.render(renderExternalWalletButtons());
1814
+ externalWalletMountedRef.current = true;
1815
+ }
1816
+ else {
1817
+ try {
1818
+ // If we have an old root but the container changed or is invalid, unmount it first
1819
+ if (externalWalletRootRef.current) {
1820
+ try {
1821
+ // Attempt to unmount if possible, though react-dom/client roots don't have unmount in the same way as legacy
1822
+ // But we should at least clear the ref
1823
+ externalWalletRootRef.current.unmount();
1824
+ }
1825
+ catch (e) {
1826
+ // Ignore unmount errors
1827
+ }
1828
+ externalWalletRootRef.current = null;
1829
+ }
1830
+ const reactDomClient = await import('react-dom/client');
1831
+ const rootInstance = reactDomClient.createRoot(container);
1832
+ externalWalletRootRef.current = rootInstance;
1833
+ externalWalletContainerRef.current = container; // Track the container
1834
+ rootInstance.render(renderExternalWalletButtons());
1835
+ externalWalletMountedRef.current = true;
1836
+ }
1837
+ catch (error) {
1838
+ externalWalletMountedRef.current = false;
1839
+ }
1840
+ }
1841
+ }, []);
1842
+ // Mount ExternalWalletButtons into OnboardingUI container (only when wagmi is available)
1843
+ useEffect(() => {
1844
+ if (!externalWalletsEnabled) {
1845
+ // When disabled, ensure container is hidden
1846
+ const onboardingAny = onboardingRef.current;
1847
+ if (onboardingAny?.externalWalletContainer) {
1848
+ onboardingAny.externalWalletContainer.style.display = 'none';
1849
+ }
1850
+ if (onboardingAny?.externalWalletDivider) {
1851
+ onboardingAny.externalWalletDivider.style.display = 'none';
1852
+ }
1853
+ return;
1854
+ }
1855
+ if (!onboardingRef.current) {
1856
+ console.log('OnboardingUI not initialized yet');
1857
+ return;
1858
+ }
1859
+ if (!wagmiConnect) {
1860
+ console.log('Wagmi connect not available');
1861
+ return;
1862
+ }
1863
+ if (!wagmiConnect.connectors || wagmiConnect.connectors.length === 0) {
1864
+ console.log('Wagmi connectors not ready yet');
1865
+ return;
1866
+ }
1867
+ if (availableConnectors.length === 0) {
1868
+ // console.log('No available connectors');
1869
+ // Don't return early - still mount the component so it can update when connectors become available
1870
+ // return;
1871
+ }
1872
+ let retryCount = 0;
1873
+ const maxRetries = 50; // Try for up to 2.5 seconds
1874
+ const mountExternalWallets = async () => {
1875
+ const onboardingAny = onboardingRef.current;
1876
+ if (!onboardingAny?.externalWalletContainer) {
1877
+ retryCount++;
1878
+ if (retryCount < maxRetries) {
1879
+ // Retry after a short delay if container not ready
1880
+ setTimeout(mountExternalWallets, 50); // Reduced from 100ms to 50ms
1881
+ }
1882
+ return;
1883
+ }
1884
+ try {
1885
+ const container = onboardingAny.externalWalletContainer;
1886
+ if (!container) {
1887
+ console.warn('External wallet container is null');
1888
+ return;
1889
+ }
1890
+ // Check if we're on OTP screen - if so, don't show external wallets
1891
+ // Check if OTP screen exists AND is actually visible in the DOM
1892
+ const isOtpScreen = onboardingAny.otpVerificationScreen &&
1893
+ onboardingAny.otpVerificationScreen.parentElement &&
1894
+ onboardingAny.otpVerificationScreen.offsetParent !== null;
1895
+ if (!isOtpScreen) {
1896
+ // Only show external wallets if not on OTP screen
1897
+ // Force container to be visible when external wallets are enabled
1898
+ container.style.display = '';
1899
+ // Show divider only if both email/Google AND external wallets are visible
1900
+ const authMethods = onboardingAny.config?.authMethods || ['otp', 'google'];
1901
+ const showEmail = authMethods.includes('otp');
1902
+ const showGoogle = authMethods.includes('google');
1903
+ const hasEmailOrGoogle = showEmail || showGoogle;
1904
+ if (onboardingAny.externalWalletDivider && hasEmailOrGoogle) {
1905
+ onboardingAny.externalWalletDivider.style.display = '';
1906
+ }
1907
+ else if (onboardingAny.externalWalletDivider) {
1908
+ onboardingAny.externalWalletDivider.style.display = 'none';
1909
+ }
1910
+ }
1911
+ else {
1912
+ // On OTP screen - hide external wallets
1913
+ container.style.display = 'none';
1914
+ if (onboardingAny.externalWalletDivider) {
1915
+ onboardingAny.externalWalletDivider.style.display = 'none';
1916
+ }
1917
+ }
1918
+ // Always unmount and create fresh mount when config changes
1919
+ // This ensures buttons appear immediately without requiring a refresh
1920
+ if (externalWalletRootRef.current) {
1921
+ try {
1922
+ externalWalletRootRef.current.unmount();
1923
+ }
1924
+ catch (e) {
1925
+ // Ignore unmount errors
1926
+ }
1927
+ externalWalletRootRef.current = null;
1928
+ externalWalletContainerRef.current = null;
1929
+ }
1930
+ // Create fresh mount - this ensures buttons appear when config changes
1931
+ const reactDomClient = await import('react-dom/client');
1932
+ const rootInstance = reactDomClient.createRoot(container);
1933
+ externalWalletRootRef.current = rootInstance;
1934
+ externalWalletContainerRef.current = container; // Track the container
1935
+ // Render the ExternalWalletButtons component
1936
+ rootInstance.render(_jsx(AbstraxnContext.Provider, { value: valueRef.current, children: _jsx(ExternalWalletButtons, { onConnect: async (_connectorId, _address) => {
1937
+ // console.log('External wallet connected:', connectorId, address);
1938
+ // Modal will be closed automatically by the useEffect that watches wagmiAccount
1939
+ }, onShowMoreWallets: () => {
1940
+ // Hide email and Google sections when "More wallets" is clicked
1941
+ const onboardingAny = onboardingRef.current;
1942
+ if (onboardingAny?.emailForm) {
1943
+ onboardingAny.emailForm.style.display = 'none';
1944
+ }
1945
+ if (onboardingAny?.googleButton) {
1946
+ onboardingAny.googleButton.style.display = 'none';
1947
+ }
1948
+ // Hide all passkey elements when wallet selection screen is shown
1949
+ if (onboardingAny?.passkeyLoginButton) {
1950
+ onboardingAny.passkeyLoginButton.style.display = 'none';
1951
+ }
1952
+ if (onboardingAny?.passkeySignupLink) {
1953
+ onboardingAny.passkeySignupLink.style.display = 'none';
1954
+ }
1955
+ if (onboardingAny?.passkeyErrorElement) {
1956
+ onboardingAny.passkeyErrorElement.style.display = 'none';
1957
+ }
1958
+ if (onboardingAny?.passkeyDivider) {
1959
+ onboardingAny.passkeyDivider.style.display = 'none';
1960
+ }
1961
+ if (onboardingAny?.externalWalletDivider) {
1962
+ onboardingAny.externalWalletDivider.style.display = 'none';
1963
+ }
1964
+ // Also hide the divider between email and Google if it exists
1965
+ if (onboardingAny?.divider) {
1966
+ onboardingAny.divider.style.display = 'none';
1967
+ }
1968
+ }, onHideMoreWallets: () => {
1969
+ // Show email and Google sections when back is clicked (restore initial screen)
1970
+ const onboardingAny = onboardingRef.current;
1971
+ const authMethods = onboardingAny?.config?.authMethods || ['otp', 'google'];
1972
+ const showEmail = authMethods.includes('otp');
1973
+ const showGoogle = authMethods.includes('google');
1974
+ const showPasskey = authMethods.includes('passkey');
1975
+ // Show email form if enabled
1976
+ if (onboardingAny?.emailForm) {
1977
+ onboardingAny.emailForm.style.display = showEmail ? '' : 'none';
1978
+ }
1979
+ // Show Google button if enabled
1980
+ if (onboardingAny?.googleButton) {
1981
+ onboardingAny.googleButton.style.display = showGoogle ? '' : 'none';
1982
+ }
1983
+ // Show passkey controls if enabled
1984
+ if (onboardingAny?.passkeyLoginButton) {
1985
+ onboardingAny.passkeyLoginButton.style.display = showPasskey ? '' : 'none';
1986
+ }
1987
+ if (onboardingAny?.passkeySignupLink) {
1988
+ onboardingAny.passkeySignupLink.style.display = showPasskey ? '' : 'none';
1989
+ }
1990
+ if (onboardingAny?.passkeyErrorElement) {
1991
+ onboardingAny.passkeyErrorElement.style.display = 'none';
1992
+ }
1993
+ // Show passkey divider if passkey is enabled and there are other auth methods
1994
+ if (onboardingAny?.passkeyDivider) {
1995
+ onboardingAny.passkeyDivider.style.display = (showPasskey && (showEmail || showGoogle)) ? '' : 'none';
1996
+ }
1997
+ // Show divider between email and Google if both are enabled
1998
+ if (onboardingAny?.divider) {
1999
+ onboardingAny.divider.style.display = (showEmail && showGoogle) ? '' : 'none';
2000
+ }
2001
+ // Show the "or" divider before external wallets only if email/Google are visible
2002
+ const hasEmailOrGoogle = showEmail || showGoogle;
2003
+ if (onboardingAny?.externalWalletDivider && hasEmailOrGoogle) {
2004
+ onboardingAny.externalWalletDivider.style.display = '';
2005
+ }
2006
+ else if (onboardingAny?.externalWalletDivider) {
2007
+ onboardingAny.externalWalletDivider.style.display = 'none';
2008
+ }
2009
+ } }) }));
2010
+ externalWalletMountedRef.current = true;
2011
+ }
2012
+ catch (error) {
2013
+ console.error('Failed to mount ExternalWalletButtons:', error);
2014
+ externalWalletMountedRef.current = false;
2015
+ }
2016
+ };
2017
+ // When externalWalletsEnabled changes, force a fresh mount
2018
+ // This ensures buttons appear immediately without requiring a refresh
2019
+ const forceRemount = () => {
2020
+ // Unmount existing root if it exists
2021
+ if (externalWalletRootRef.current) {
2022
+ try {
2023
+ externalWalletRootRef.current.unmount();
2024
+ }
2025
+ catch (e) {
2026
+ // Ignore unmount errors
2027
+ }
2028
+ externalWalletRootRef.current = null;
2029
+ }
2030
+ externalWalletContainerRef.current = null;
2031
+ externalWalletMountedRef.current = false;
2032
+ };
2033
+ // Start mounting immediately - no delay needed
2034
+ mountExternalWallets();
2035
+ return () => {
2036
+ // Cleanup if needed
2037
+ };
2038
+ }, [externalWalletsEnabled, wagmiConnect, wagmiConnect?.connectors, availableConnectors.length]);
2039
+ // Update the rendered component when value changes (but don't re-mount)
2040
+ useEffect(() => {
2041
+ if (!externalWalletsEnabled)
2042
+ return;
2043
+ if (!externalWalletRootRef.current || !externalWalletMountedRef.current)
2044
+ return;
2045
+ // Update the rendered component with latest value
2046
+ try {
2047
+ externalWalletRootRef.current.render(_jsx(AbstraxnContext.Provider, { value: valueRef.current, children: _jsx(ExternalWalletButtons, { onConnect: async (_connectorId, _address) => {
2048
+ // console.log('External wallet connected:', connectorId, address);
2049
+ }, onShowMoreWallets: () => {
2050
+ // Hide email and Google sections when "More wallets" is clicked
2051
+ const onboardingAny = onboardingRef.current;
2052
+ if (onboardingAny?.emailForm) {
2053
+ onboardingAny.emailForm.style.display = 'none';
2054
+ }
2055
+ if (onboardingAny?.googleButton) {
2056
+ onboardingAny.googleButton.style.display = 'none';
2057
+ }
2058
+ // Hide Discord and X (Twitter) buttons when wallet selection screen is shown
2059
+ if (onboardingAny?.discordButton) {
2060
+ onboardingAny.discordButton.style.display = 'none';
2061
+ }
2062
+ if (onboardingAny?.twitterButton) {
2063
+ onboardingAny.twitterButton.style.display = 'none';
2064
+ }
2065
+ // Hide social grid if it exists
2066
+ if (onboardingAny?.socialGrid) {
2067
+ onboardingAny.socialGrid.style.display = 'none';
2068
+ }
2069
+ // Extra guard: hide any social icon buttons by class
2070
+ if (onboardingAny?.rootElement) {
2071
+ onboardingAny.rootElement
2072
+ .querySelectorAll('.onboarding-button-social-icon, .onboarding-button-social')
2073
+ .forEach((el) => {
2074
+ el.style.display = 'none';
2075
+ });
2076
+ }
2077
+ // Hide all passkey elements when wallet selection screen is shown
2078
+ if (onboardingAny?.passkeyLoginButton) {
2079
+ onboardingAny.passkeyLoginButton.style.display = 'none';
2080
+ }
2081
+ if (onboardingAny?.passkeySignupLink) {
2082
+ onboardingAny.passkeySignupLink.style.display = 'none';
2083
+ }
2084
+ if (onboardingAny?.passkeyErrorElement) {
2085
+ onboardingAny.passkeyErrorElement.style.display = 'none';
2086
+ }
2087
+ if (onboardingAny?.passkeyDivider) {
2088
+ onboardingAny.passkeyDivider.style.display = 'none';
2089
+ }
2090
+ if (onboardingAny?.externalWalletDivider) {
2091
+ onboardingAny.externalWalletDivider.style.display = 'none';
2092
+ }
2093
+ // Also hide the divider between email and Google if it exists
2094
+ if (onboardingAny?.divider) {
2095
+ onboardingAny.divider.style.display = 'none';
2096
+ }
2097
+ }, onHideMoreWallets: () => {
2098
+ // Show email and Google sections when back is clicked
2099
+ const onboardingAny = onboardingRef.current;
2100
+ const authMethods = onboardingAny?.config?.authMethods || ['otp', 'google'];
2101
+ const showEmail = authMethods.includes('otp');
2102
+ const showGoogle = authMethods.includes('google');
2103
+ const showTwitter = authMethods.includes('twitter');
2104
+ const showDiscord = authMethods.includes('discord');
2105
+ const showPasskey = authMethods.includes('passkey');
2106
+ if (onboardingAny?.emailForm) {
2107
+ onboardingAny.emailForm.style.display = showEmail ? '' : 'none';
2108
+ }
2109
+ if (onboardingAny?.googleButton) {
2110
+ onboardingAny.googleButton.style.display = showGoogle ? '' : 'none';
2111
+ }
2112
+ // Show Discord and X (Twitter) buttons if enabled
2113
+ if (onboardingAny?.discordButton) {
2114
+ onboardingAny.discordButton.style.display = showDiscord ? '' : 'none';
2115
+ }
2116
+ if (onboardingAny?.twitterButton) {
2117
+ onboardingAny.twitterButton.style.display = showTwitter ? '' : 'none';
2118
+ }
2119
+ // Show social grid if it exists and social methods are enabled
2120
+ if (onboardingAny?.socialGrid) {
2121
+ const hasSocialMethods = showGoogle || showTwitter || showDiscord;
2122
+ onboardingAny.socialGrid.style.display = hasSocialMethods ? '' : 'none';
2123
+ }
2124
+ // Extra guard: restore social icon buttons by class based on flags
2125
+ if (onboardingAny?.rootElement) {
2126
+ const hasSocialMethods = showGoogle || showTwitter || showDiscord;
2127
+ onboardingAny.rootElement
2128
+ .querySelectorAll('.onboarding-button-social-icon, .onboarding-button-social')
2129
+ .forEach((el) => {
2130
+ el.style.display = hasSocialMethods ? '' : 'none';
2131
+ });
2132
+ }
2133
+ // Show passkey controls if enabled
2134
+ if (onboardingAny?.passkeyLoginButton) {
2135
+ onboardingAny.passkeyLoginButton.style.display = showPasskey ? '' : 'none';
2136
+ }
2137
+ if (onboardingAny?.passkeySignupLink) {
2138
+ onboardingAny.passkeySignupLink.style.display = showPasskey ? '' : 'none';
2139
+ }
2140
+ if (onboardingAny?.passkeyErrorElement) {
2141
+ onboardingAny.passkeyErrorElement.style.display = 'none';
2142
+ }
2143
+ // Show passkey divider if passkey is enabled and there are other auth methods
2144
+ if (onboardingAny?.passkeyDivider) {
2145
+ onboardingAny.passkeyDivider.style.display = (showPasskey && (showEmail || showGoogle || showTwitter || showDiscord)) ? '' : 'none';
2146
+ }
2147
+ // Show divider between email and social methods if both are enabled
2148
+ if (onboardingAny?.divider) {
2149
+ const hasSocialMethods = showGoogle || showTwitter || showDiscord;
2150
+ onboardingAny.divider.style.display = (showEmail && hasSocialMethods) ? '' : 'none';
2151
+ }
2152
+ if (onboardingAny?.externalWalletDivider) {
2153
+ onboardingAny.externalWalletDivider.style.display = '';
2154
+ }
2155
+ } }) }));
2156
+ }
2157
+ catch (error) {
2158
+ console.warn('Failed to update ExternalWalletButtons:', error);
2159
+ }
2160
+ }, [value, externalWalletsEnabled]);
2161
+ return (_jsx(AbstraxnContext.Provider, { value: value, children: children }));
2162
+ }
2163
+ /**
2164
+ * Main AbstraxnProvider component with optional WagmiProvider wrapper
2165
+ */
2166
+ export function AbstraxnProvider({ config, children }) {
2167
+ const externalWalletsEnabled = config.externalWallets?.enabled ?? false;
2168
+ // Create wagmi config if external wallets are enabled
2169
+ // Use useMemo to ensure config is created synchronously during render
2170
+ // This prevents the "Loading wallet connectors..." state from flashing or blocking initial render
2171
+ const { wagmiConfig, configError } = useMemo(() => {
2172
+ if (!externalWalletsEnabled)
2173
+ return { wagmiConfig: null, configError: null };
2174
+ try {
2175
+ const configResult = createWagmiConfig(config.supportedChains, config.externalWallets?.walletConnectProjectId, config.externalWallets?.connectors, config.ui?.theme || 'dark');
2176
+ return { wagmiConfig: configResult, configError: null };
2177
+ }
2178
+ catch (error) {
2179
+ console.error('Failed to create wagmi config:', error);
2180
+ return {
2181
+ wagmiConfig: null,
2182
+ configError: error instanceof Error ? error : new Error('Failed to create wagmi config')
2183
+ };
2184
+ }
2185
+ }, [externalWalletsEnabled, config.supportedChains, config.externalWallets?.walletConnectProjectId, config.externalWallets?.connectors]);
2186
+ // If external wallets are enabled, wrap with QueryClientProvider and WagmiProvider
2187
+ if (externalWalletsEnabled) {
2188
+ if (configError) {
2189
+ console.error('Failed to create wagmi config:', configError);
2190
+ // Fallback to provider without wagmi if config creation fails
2191
+ return _jsx(AbstraxnProviderWithoutWagmi, { config: config, children: children });
2192
+ }
2193
+ if (!wagmiConfig) {
2194
+ // Show loading state while config is being created
2195
+ // Don't render AbstraxnProviderInner until wagmiConfig is ready
2196
+ return (_jsx(QueryClientProvider, { client: defaultQueryClient, children: _jsx("div", { style: { display: 'none' }, children: "Loading wallet connectors..." }) }));
2197
+ }
2198
+ return (_jsx(QueryClientProvider, { client: defaultQueryClient, children: _jsx(WagmiProvider, { config: wagmiConfig, children: _jsx(AbstraxnProviderWithWagmi, { config: config, children: children }) }) }));
2199
+ }
2200
+ // If external wallets are disabled, use the provider without wagmi
2201
+ return _jsx(AbstraxnProviderWithoutWagmi, { config: config, children: children });
2202
+ }
2203
+ /**
2204
+ * Hook to access Abstraxn Wallet context
2205
+ */
2206
+ export function useAbstraxnWallet() {
2207
+ const context = useContext(AbstraxnContext);
2208
+ if (!context) {
2209
+ throw new Error('useAbstraxnWallet must be used within AbstraxnProvider');
2210
+ }
2211
+ return context;
2212
+ }
2213
+ //# sourceMappingURL=AbstraxnProvider.js.map