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.
- package/README.md +114 -0
- package/dist/src/AbstraxnProvider.d.ts +20 -0
- package/dist/src/AbstraxnProvider.js +2213 -0
- package/dist/src/AbstraxnProvider.js.map +1 -0
- package/dist/src/ConnectButton.css +217 -0
- package/dist/src/ConnectButton.d.ts +71 -0
- package/dist/src/ConnectButton.js +102 -0
- package/dist/src/ConnectButton.js.map +1 -0
- package/dist/src/ExternalWalletButtons.css +319 -0
- package/dist/src/ExternalWalletButtons.d.ts +56 -0
- package/dist/src/ExternalWalletButtons.js +245 -0
- package/dist/src/ExternalWalletButtons.js.map +1 -0
- package/dist/src/OnboardingUI.d.ts +63 -0
- package/dist/src/OnboardingUI.js +66 -0
- package/dist/src/OnboardingUI.js.map +1 -0
- package/dist/src/WalletModal.css +549 -0
- package/dist/src/WalletModal.d.ts +6 -0
- package/dist/src/WalletModal.js +89 -0
- package/dist/src/WalletModal.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUI.css +727 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +15 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js +65 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +257 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +3454 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +16 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.js +19 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/Modal.d.ts +15 -0
- package/dist/src/components/OnboardingUI/components/Modal.js +68 -0
- package/dist/src/components/OnboardingUI/components/Modal.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +19 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.js +58 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +14 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js +22 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +15 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.js +15 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/index.d.ts +13 -0
- package/dist/src/components/OnboardingUI/components/index.js +9 -0
- package/dist/src/components/OnboardingUI/components/index.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/index.d.ts +7 -0
- package/dist/src/components/OnboardingUI/hooks/index.js +6 -0
- package/dist/src/components/OnboardingUI/hooks/index.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.d.ts +11 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +146 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +21 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +135 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -0
- package/dist/src/components/OnboardingUI/index.d.ts +12 -0
- package/dist/src/components/OnboardingUI/index.js +15 -0
- package/dist/src/components/OnboardingUI/index.js.map +1 -0
- package/dist/src/hooks.d.ts +204 -0
- package/dist/src/hooks.js +394 -0
- package/dist/src/hooks.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +181 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/wagmiConfig.d.ts +147 -0
- package/dist/src/wagmiConfig.js +81 -0
- package/dist/src/wagmiConfig.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- 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
|