signer-test-sdk-react 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +4 -0
- package/dist/src/AbstraxnProvider.d.ts +5 -13
- package/dist/src/AbstraxnProvider.js +14 -3126
- package/dist/src/AbstraxnProvider.js.map +1 -1
- package/dist/src/ConnectButton.css +1 -1
- package/dist/src/ExternalWalletButtons.css +1 -1
- package/dist/src/ExternalWalletButtons.js +2 -2
- package/dist/src/ExternalWalletButtons.js.map +1 -1
- package/dist/src/OnboardingUI.d.ts +1 -1
- package/dist/src/WalletModal.css +193 -373
- package/dist/src/WalletModal.d.ts +1 -1
- package/dist/src/WalletModal.js +108 -45
- package/dist/src/WalletModal.js.map +1 -1
- package/dist/src/chains.d.ts +4 -3
- package/dist/src/chains.js +154 -84
- package/dist/src/chains.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/AbstraxnProvider.d.ts +12 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProvider.js +146 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProvider.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.d.ts +25 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js +3086 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithWagmi.d.ts +8 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithWagmi.js +46 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithWagmi.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithoutWagmi.d.ts +8 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithoutWagmi.js +12 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithoutWagmi.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/context.d.ts +2 -0
- package/dist/src/components/AbstraxnProvider/context.js +6 -0
- package/dist/src/components/AbstraxnProvider/context.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/index.d.ts +6 -0
- package/dist/src/components/AbstraxnProvider/index.js +7 -0
- package/dist/src/components/AbstraxnProvider/index.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.d.ts +30 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.js +49 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnWallet.d.ts +2 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnWallet.js +13 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnWallet.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.d.ts +22 -0
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js +242 -0
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.d.ts +25 -0
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js +539 -0
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/utils.d.ts +41 -0
- package/dist/src/components/AbstraxnProvider/utils.js +139 -0
- package/dist/src/components/AbstraxnProvider/utils.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUI.css +8 -5
- package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +7 -1
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -1
- package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +1 -1
- package/dist/src/components/OnboardingUI/components/EmailForm.js +1 -1
- package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -1
- package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +1 -1
- package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +1 -1
- package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +1 -1
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.d.ts +1 -1
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +1 -1
- package/dist/src/components/OnboardingUI/index.d.ts +1 -1
- package/dist/src/components/WalletModal/components/ChainSelector.css +249 -102
- package/dist/src/components/WalletModal/components/ChainSelector.d.ts +7 -6
- package/dist/src/components/WalletModal/components/ChainSelector.js +68 -27
- package/dist/src/components/WalletModal/components/ChainSelector.js.map +1 -1
- package/dist/src/components/WalletModal/components/ExportKeyModal.css +89 -88
- package/dist/src/components/WalletModal/components/ExportKeyModal.d.ts +6 -1
- package/dist/src/components/WalletModal/components/ExportKeyModal.js +6 -11
- package/dist/src/components/WalletModal/components/ExportKeyModal.js.map +1 -1
- package/dist/src/components/WalletModal/components/ExportWarningModal.css +107 -2
- package/dist/src/components/WalletModal/components/ExportWarningModal.d.ts +7 -1
- package/dist/src/components/WalletModal/components/ExportWarningModal.js +5 -3
- package/dist/src/components/WalletModal/components/ExportWarningModal.js.map +1 -1
- package/dist/src/components/WalletModal/components/ManageWalletModal.css +90 -4
- package/dist/src/components/WalletModal/components/ManageWalletModal.d.ts +3 -3
- package/dist/src/components/WalletModal/components/ManageWalletModal.js +28 -13
- package/dist/src/components/WalletModal/components/ManageWalletModal.js.map +1 -1
- package/dist/src/components/WalletModal/components/PreviewTransactionModal.css +3 -4
- package/dist/src/components/WalletModal/components/ReceiveModal.css +93 -58
- package/dist/src/components/WalletModal/components/ReceiveModal.js +1 -1
- package/dist/src/components/WalletModal/components/ReceiveModal.js.map +1 -1
- package/dist/src/components/WalletModal/components/SendModal.css +170 -127
- package/dist/src/components/WalletModal/components/SendModal.d.ts +4 -6
- package/dist/src/components/WalletModal/components/SendModal.js +131 -39
- package/dist/src/components/WalletModal/components/SendModal.js.map +1 -1
- package/dist/src/components/WalletModal/components/SuccessModal.css +7 -8
- package/dist/src/components/WalletModal/components/TokenSelectorModal.css +240 -0
- package/dist/src/components/WalletModal/components/TokenSelectorModal.d.ts +21 -0
- package/dist/src/components/WalletModal/components/TokenSelectorModal.js +44 -0
- package/dist/src/components/WalletModal/components/TokenSelectorModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/UserAvatar.d.ts +1 -1
- package/dist/src/components/WalletModal/components/index.d.ts +2 -0
- package/dist/src/components/WalletModal/components/index.js +1 -0
- package/dist/src/components/WalletModal/components/index.js.map +1 -1
- package/dist/src/components/WalletModal/hooks/useSendTransaction.d.ts +1 -1
- package/dist/src/hooks.d.ts +402 -362
- package/dist/src/hooks.js +486 -244
- package/dist/src/hooks.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +6 -6
- package/dist/src/wagmiConfig.d.ts +2 -2
- package/dist/src/wagmiConfig.js +34 -21
- package/dist/src/wagmiConfig.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,3086 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* AbstraxnProviderInner - Core provider logic
|
|
4
|
+
* This component contains all the wallet state management and operations
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useState, useRef, useCallback, useMemo, } from "react";
|
|
7
|
+
import { AbstraxnWallet, AuthenticationError, } from "@abstraxn/signer-core";
|
|
8
|
+
import { OnboardingUIWeb } from "../OnboardingUI";
|
|
9
|
+
import { parseEther, createPublicClient, http } from "viem";
|
|
10
|
+
import { ExternalWalletButtons } from "../../ExternalWalletButtons";
|
|
11
|
+
import { EVM_CHAINS, SOLANA_CHAINS, getChainById, toCoreChain, } from "../../chains";
|
|
12
|
+
import { AbstraxnContext } from "./context";
|
|
13
|
+
export function AbstraxnProviderInner({ config, children, base, wagmi, }) {
|
|
14
|
+
const { isInitialized, setIsInitialized, isConnected, setIsConnected, address, setAddress, user, setUser, whoami, setWhoami, chainId, setChainId, error, setError, loading, setLoading, walletBalance, setWalletBalance, onboardingRef, otpIdRef, walletRef, googleCallbackHandledRef, twitterCallbackHandledRef, discordCallbackHandledRef, } = base;
|
|
15
|
+
const externalWalletsEnabled = config.externalWallets?.enabled ?? false;
|
|
16
|
+
// Keep a ref to avoid re-creating callbacks when toggling config (prevents flicker)
|
|
17
|
+
const externalWalletsEnabledRef = useRef(externalWalletsEnabled);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
externalWalletsEnabledRef.current = externalWalletsEnabled;
|
|
20
|
+
}, [externalWalletsEnabled]);
|
|
21
|
+
const wagmiAccount = wagmi?.wagmiAccount || null;
|
|
22
|
+
const wagmiConnect = wagmi?.wagmiConnect || null;
|
|
23
|
+
const wagmiDisconnect = wagmi?.wagmiDisconnect || null;
|
|
24
|
+
const wagmiSignMessage = wagmi?.wagmiSignMessage || null;
|
|
25
|
+
const wagmiSendTransaction = wagmi?.wagmiSendTransaction || null;
|
|
26
|
+
const wagmiSwitchChain = wagmi?.wagmiSwitchChain || null;
|
|
27
|
+
const wagmiBalance = wagmi?.wagmiBalance || null;
|
|
28
|
+
const wagmiChainIdHook = wagmi?.wagmiChainIdHook || null;
|
|
29
|
+
// External wallet state
|
|
30
|
+
const [isExternalWalletConnected, setIsExternalWalletConnected] = useState(false);
|
|
31
|
+
const [externalWalletAddress, setExternalWalletAddress] = useState(null);
|
|
32
|
+
const [externalWalletChainId, setExternalWalletChainId] = useState(null);
|
|
33
|
+
// Connection type tracking: 'google' | 'email' | 'discord' | 'x' | 'passkey' | 'metamask' | 'walletconnect' | 'coinbase' | 'phantom' | 'injected' | null
|
|
34
|
+
const [connectionType, setConnectionType] = useState(null);
|
|
35
|
+
const explicitConnectionRef = useRef(false);
|
|
36
|
+
const autoDisconnectHandledRef = useRef(false);
|
|
37
|
+
// Track when we last connected to prevent premature reset
|
|
38
|
+
const lastConnectionTimeRef = useRef(0);
|
|
39
|
+
// Refs to track previous values and prevent unnecessary updates
|
|
40
|
+
const lastAddressRef = useRef(null);
|
|
41
|
+
const lastChainIdRef = useRef(null);
|
|
42
|
+
const isUpdatingRef = useRef(false);
|
|
43
|
+
const connectionTimeoutRef = useRef(null);
|
|
44
|
+
// Track if we're in the middle of a disconnect to prevent useEffect from interfering
|
|
45
|
+
const disconnectingRef = useRef(false);
|
|
46
|
+
// Show/hide external wallet UI when config changes (prevents modal flicker)
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const onboardingAny = onboardingRef.current;
|
|
49
|
+
if (!onboardingAny)
|
|
50
|
+
return;
|
|
51
|
+
requestAnimationFrame(() => {
|
|
52
|
+
if (externalWalletsEnabled) {
|
|
53
|
+
// Show external wallet container when enabled
|
|
54
|
+
if (onboardingAny.externalWalletContainer) {
|
|
55
|
+
onboardingAny.externalWalletContainer.style.display = "";
|
|
56
|
+
}
|
|
57
|
+
// Show divider if email/Google are also visible
|
|
58
|
+
const authMethods = onboardingAny.config?.authMethods || [
|
|
59
|
+
"otp",
|
|
60
|
+
"google",
|
|
61
|
+
];
|
|
62
|
+
const showEmail = authMethods.includes("otp");
|
|
63
|
+
const showGoogle = authMethods.includes("google");
|
|
64
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
65
|
+
if (onboardingAny.externalWalletDivider && hasEmailOrGoogle) {
|
|
66
|
+
onboardingAny.externalWalletDivider.style.display = "";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Hide external wallet container when disabled
|
|
71
|
+
if (onboardingAny.externalWalletContainer) {
|
|
72
|
+
onboardingAny.externalWalletContainer.style.display = "none";
|
|
73
|
+
}
|
|
74
|
+
if (onboardingAny.externalWalletDivider) {
|
|
75
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}, [externalWalletsEnabled]);
|
|
80
|
+
// Restore external wallet connection on mount if wagmi has already restored it
|
|
81
|
+
// This runs after wagmi has had time to restore from localStorage
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!externalWalletsEnabled || !wagmiAccount) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Check if wagmi has already restored a connection from localStorage
|
|
87
|
+
// This happens automatically with wagmi's persistence when storage is configured
|
|
88
|
+
const checkAndRestore = () => {
|
|
89
|
+
if (wagmiAccount.isConnected &&
|
|
90
|
+
wagmiAccount.address &&
|
|
91
|
+
!isExternalWalletConnected) {
|
|
92
|
+
const walletAddress = wagmiAccount.address.toLowerCase();
|
|
93
|
+
const currentChainId = wagmiChainIdHook || wagmiAccount.chainId || null;
|
|
94
|
+
// Set explicitConnectionRef FIRST to prevent auto-disconnect from interfering
|
|
95
|
+
explicitConnectionRef.current = true;
|
|
96
|
+
autoDisconnectHandledRef.current = false;
|
|
97
|
+
// Restore external wallet state
|
|
98
|
+
setIsExternalWalletConnected(true);
|
|
99
|
+
setExternalWalletAddress(walletAddress);
|
|
100
|
+
setAddress(walletAddress);
|
|
101
|
+
setIsConnected(true);
|
|
102
|
+
setExternalWalletChainId(currentChainId);
|
|
103
|
+
setChainId(currentChainId);
|
|
104
|
+
// Restore connection type from connector
|
|
105
|
+
const connector = wagmiAccount.connector;
|
|
106
|
+
if (connector) {
|
|
107
|
+
const connectorId = connector.id.toLowerCase();
|
|
108
|
+
const connectorName = connector.name?.toLowerCase() || "";
|
|
109
|
+
// Map connector to connection type
|
|
110
|
+
if (connectorId.includes("metamask") ||
|
|
111
|
+
connectorId.includes("io.metamask") ||
|
|
112
|
+
connectorName.includes("metamask")) {
|
|
113
|
+
setConnectionType("metamask");
|
|
114
|
+
}
|
|
115
|
+
else if (connectorId.includes("walletconnect") ||
|
|
116
|
+
connectorId.includes("wallet_connect") ||
|
|
117
|
+
connectorName.includes("walletconnect")) {
|
|
118
|
+
setConnectionType("walletconnect");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// For other wallets (coinbase, phantom, injected), store as-is
|
|
122
|
+
// The hook will normalize them, but we keep the original for reference
|
|
123
|
+
const fallbackType = connectorName || connectorId;
|
|
124
|
+
setConnectionType(fallbackType);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Try to restore from localStorage as fallback
|
|
129
|
+
try {
|
|
130
|
+
const storedType = localStorage.getItem("abstraxn_connection_type");
|
|
131
|
+
if (storedType) {
|
|
132
|
+
setConnectionType(storedType);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
// Ignore localStorage errors
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Update refs to track the restored connection
|
|
140
|
+
lastAddressRef.current = walletAddress;
|
|
141
|
+
lastChainIdRef.current = currentChainId;
|
|
142
|
+
lastConnectionTimeRef.current = Date.now();
|
|
143
|
+
return true; // Indicate restoration happened
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
};
|
|
147
|
+
// Check immediately
|
|
148
|
+
if (checkAndRestore()) {
|
|
149
|
+
return; // Already restored
|
|
150
|
+
}
|
|
151
|
+
// Also check after delays to catch wagmi's async restoration
|
|
152
|
+
// Wagmi might restore the connection asynchronously after the component mounts
|
|
153
|
+
let timeout2 = null;
|
|
154
|
+
const timeout1 = setTimeout(() => {
|
|
155
|
+
if (!checkAndRestore()) {
|
|
156
|
+
// Check one more time after a longer delay
|
|
157
|
+
timeout2 = setTimeout(() => {
|
|
158
|
+
checkAndRestore();
|
|
159
|
+
}, 300);
|
|
160
|
+
}
|
|
161
|
+
}, 150);
|
|
162
|
+
return () => {
|
|
163
|
+
clearTimeout(timeout1);
|
|
164
|
+
if (timeout2) {
|
|
165
|
+
clearTimeout(timeout2);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}, [
|
|
169
|
+
externalWalletsEnabled,
|
|
170
|
+
wagmiAccount?.isConnected,
|
|
171
|
+
wagmiAccount?.address,
|
|
172
|
+
wagmiChainIdHook,
|
|
173
|
+
isExternalWalletConnected,
|
|
174
|
+
]); // Re-run if wagmi state changes
|
|
175
|
+
// Initialize wallet
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
// If wallet already exists (from previous mount), reuse it to prevent flicker
|
|
178
|
+
if (walletRef.current) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Compute supported chains from chain config (outside useMemo since we're in useEffect)
|
|
182
|
+
// This should include ALL chains that might be shown in the UI (availableChains)
|
|
183
|
+
// We use the same logic as availableChains to ensure consistency
|
|
184
|
+
let computedSupportedChains = undefined;
|
|
185
|
+
if (config.supportedChains) {
|
|
186
|
+
// Use explicitly provided chains
|
|
187
|
+
computedSupportedChains = config.supportedChains;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Compute from chains config - use same logic as availableChains
|
|
191
|
+
const chainConfig = config.chains;
|
|
192
|
+
const chains = [];
|
|
193
|
+
// Priority 1: Check if chains is an array of viem chain objects (direct format)
|
|
194
|
+
if (Array.isArray(chainConfig) && chainConfig?.length > 0) {
|
|
195
|
+
const firstItem = chainConfig[0];
|
|
196
|
+
if (firstItem &&
|
|
197
|
+
typeof firstItem.id === "number" &&
|
|
198
|
+
firstItem.nativeCurrency) {
|
|
199
|
+
// Convert viem chain objects to core chain format
|
|
200
|
+
chainConfig.forEach((viemChain) => {
|
|
201
|
+
let rpcUrl = "";
|
|
202
|
+
if (viemChain.rpcUrls?.default?.http) {
|
|
203
|
+
rpcUrl = Array.isArray(viemChain.rpcUrls.default.http)
|
|
204
|
+
? viemChain.rpcUrls.default.http[0]
|
|
205
|
+
: viemChain.rpcUrls.default.http;
|
|
206
|
+
}
|
|
207
|
+
else if (viemChain.rpcUrls?.public?.http) {
|
|
208
|
+
rpcUrl = Array.isArray(viemChain.rpcUrls.public.http)
|
|
209
|
+
? viemChain.rpcUrls.public.http[0]
|
|
210
|
+
: viemChain.rpcUrls.public.http;
|
|
211
|
+
}
|
|
212
|
+
chains.push({
|
|
213
|
+
id: viemChain.id,
|
|
214
|
+
name: viemChain.name,
|
|
215
|
+
rpcUrl: rpcUrl ||
|
|
216
|
+
`https://rpc.ankr.com/${viemChain.name.toLowerCase()}`,
|
|
217
|
+
nativeCurrency: viemChain.nativeCurrency,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Priority 2: Check new chains config format
|
|
223
|
+
else if (chainConfig?.supportedEvmChains &&
|
|
224
|
+
chainConfig.supportedEvmChains.length > 0) {
|
|
225
|
+
chainConfig.supportedEvmChains.forEach((chainName) => {
|
|
226
|
+
const chainData = EVM_CHAINS[chainName];
|
|
227
|
+
if (chainData && chainData.type === "evm") {
|
|
228
|
+
chains.push(toCoreChain(chainData));
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// Priority 3: If no specific config, include default chains that might be shown
|
|
233
|
+
// This ensures Base and other common chains are available even if not explicitly configured
|
|
234
|
+
if (chains.length === 0) {
|
|
235
|
+
// Include common chains that are likely to be in availableChains
|
|
236
|
+
chains.push(toCoreChain(EVM_CHAINS.ethereum));
|
|
237
|
+
chains.push(toCoreChain(EVM_CHAINS.polygon));
|
|
238
|
+
chains.push(toCoreChain(EVM_CHAINS.base));
|
|
239
|
+
}
|
|
240
|
+
computedSupportedChains =
|
|
241
|
+
chains.length > 0
|
|
242
|
+
? chains
|
|
243
|
+
: [
|
|
244
|
+
toCoreChain(EVM_CHAINS.ethereum),
|
|
245
|
+
toCoreChain(EVM_CHAINS.polygon),
|
|
246
|
+
toCoreChain(EVM_CHAINS.base),
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
const walletInstance = new AbstraxnWallet({
|
|
250
|
+
apiKey: config.apiKey,
|
|
251
|
+
authMethods: config.authMethods,
|
|
252
|
+
googleClientId: config.googleClientId,
|
|
253
|
+
defaultChainId: config.chains?.defaultChainId || config.defaultChainId,
|
|
254
|
+
supportedChains: computedSupportedChains,
|
|
255
|
+
autoConnect: config.autoConnect ?? false,
|
|
256
|
+
enableLogging: config.enableLogging ?? false,
|
|
257
|
+
});
|
|
258
|
+
walletRef.current = walletInstance;
|
|
259
|
+
// Set up event listeners
|
|
260
|
+
walletInstance.on("connect", async () => {
|
|
261
|
+
try {
|
|
262
|
+
// Verify whoami exists before setting connected
|
|
263
|
+
const whoamiInfo = await walletInstance.getWhoami();
|
|
264
|
+
if (!whoamiInfo) {
|
|
265
|
+
// If whoami is not available, don't set connected
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
setIsConnected(true);
|
|
269
|
+
const userInfo = await walletInstance.getUserInfo();
|
|
270
|
+
setUser(userInfo);
|
|
271
|
+
setWhoami(whoamiInfo);
|
|
272
|
+
// Try to get address, but don't fail if not available yet
|
|
273
|
+
try {
|
|
274
|
+
const addr = await walletInstance.getAddress();
|
|
275
|
+
setAddress(addr);
|
|
276
|
+
}
|
|
277
|
+
catch (addrErr) {
|
|
278
|
+
console.warn("Address not available on connect:", addrErr);
|
|
279
|
+
}
|
|
280
|
+
const cid = await walletInstance.getChainId();
|
|
281
|
+
setChainId(cid);
|
|
282
|
+
// Always restore connection type from localStorage on connect
|
|
283
|
+
// This ensures connectionType is available after OAuth redirects (Google, X, Discord)
|
|
284
|
+
// and after page refreshes
|
|
285
|
+
try {
|
|
286
|
+
const storedType = localStorage.getItem("abstraxn_connection_type");
|
|
287
|
+
if (storedType &&
|
|
288
|
+
(storedType === "google" ||
|
|
289
|
+
storedType === "email" ||
|
|
290
|
+
storedType === "discord" ||
|
|
291
|
+
storedType === "x" ||
|
|
292
|
+
storedType === "passkey")) {
|
|
293
|
+
setConnectionType(storedType);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
// Ignore localStorage errors
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
console.error("Error getting wallet info on connect:", err);
|
|
302
|
+
}
|
|
303
|
+
// Ensure loading modal is closed when connected (whoami received)
|
|
304
|
+
if (onboardingRef.current) {
|
|
305
|
+
const onboardingAny = onboardingRef.current;
|
|
306
|
+
if (onboardingAny.hideLoadingModal) {
|
|
307
|
+
onboardingAny.hideLoadingModal();
|
|
308
|
+
}
|
|
309
|
+
// Also ensure the overlay is hidden if it was opened
|
|
310
|
+
if (onboardingAny.modalOverlay &&
|
|
311
|
+
onboardingAny.modalOverlay.classList.contains("onboarding-modal-open")) {
|
|
312
|
+
onboardingAny.modalOverlay.classList.remove("onboarding-modal-open");
|
|
313
|
+
onboardingAny.modalOverlay.classList.add("onboarding-modal-closing");
|
|
314
|
+
setTimeout(() => {
|
|
315
|
+
if (onboardingAny.modalOverlay) {
|
|
316
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
317
|
+
}
|
|
318
|
+
}, 200);
|
|
319
|
+
document.body.style.overflow = "";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
walletInstance.on("disconnect", () => {
|
|
324
|
+
setIsConnected(false);
|
|
325
|
+
setAddress(null);
|
|
326
|
+
setUser(null);
|
|
327
|
+
setWhoami(null);
|
|
328
|
+
setChainId(null);
|
|
329
|
+
setConnectionType(null);
|
|
330
|
+
// Clear from localStorage
|
|
331
|
+
try {
|
|
332
|
+
localStorage.removeItem("abstraxn_connection_type");
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
// Ignore localStorage errors
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
walletInstance.on("accountChanged", (newAddress) => {
|
|
339
|
+
setAddress(newAddress);
|
|
340
|
+
});
|
|
341
|
+
walletInstance.on("chainChanged", (newChainId) => {
|
|
342
|
+
setChainId(newChainId);
|
|
343
|
+
});
|
|
344
|
+
// Initialize OnboardingUI only if not already created (prevents flicker on config change)
|
|
345
|
+
if (onboardingRef.current) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const uiConfig = config.ui || {};
|
|
349
|
+
const onboarding = new OnboardingUIWeb({
|
|
350
|
+
logo: uiConfig.logo,
|
|
351
|
+
theme: uiConfig.theme || "light",
|
|
352
|
+
showFooter: uiConfig.showFooter !== false,
|
|
353
|
+
onboardTitle: uiConfig.onboardTitle || "Sign In",
|
|
354
|
+
modal: uiConfig.modal !== false,
|
|
355
|
+
closeOnBackdropClick: uiConfig.closeOnBackdropClick !== false,
|
|
356
|
+
className: uiConfig.className,
|
|
357
|
+
style: uiConfig.style,
|
|
358
|
+
labels: uiConfig.labels,
|
|
359
|
+
colors: uiConfig.colors,
|
|
360
|
+
customCSS: uiConfig.customCSS,
|
|
361
|
+
authMethods: uiConfig.authMethods || config.authMethods,
|
|
362
|
+
onEmailOtpInitiate: async (email) => {
|
|
363
|
+
try {
|
|
364
|
+
if (!walletInstance)
|
|
365
|
+
throw new Error("Wallet not initialized");
|
|
366
|
+
const authManager = walletInstance.getAuthManager();
|
|
367
|
+
const connectionTypeValue = "email";
|
|
368
|
+
setConnectionType(connectionTypeValue);
|
|
369
|
+
try {
|
|
370
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
// Ignore localStorage errors
|
|
374
|
+
}
|
|
375
|
+
const result = await authManager.loginWithOTP(email);
|
|
376
|
+
otpIdRef.current = result.otpId;
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
console.error("OTP Init Error:", err);
|
|
380
|
+
throw err;
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
onEmailOtpVerify: async (_email, otp) => {
|
|
384
|
+
try {
|
|
385
|
+
if (!walletInstance)
|
|
386
|
+
throw new Error("Wallet not initialized");
|
|
387
|
+
if (!otpIdRef.current)
|
|
388
|
+
throw new Error("OTP ID not found");
|
|
389
|
+
// Set connection type for email OTP before verification
|
|
390
|
+
const connectionTypeValue = "email";
|
|
391
|
+
setConnectionType(connectionTypeValue);
|
|
392
|
+
try {
|
|
393
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
394
|
+
}
|
|
395
|
+
catch (e) {
|
|
396
|
+
// Ignore localStorage errors
|
|
397
|
+
}
|
|
398
|
+
const authManager = walletInstance.getAuthManager();
|
|
399
|
+
const user = await authManager.verifyOTP(otpIdRef.current, otp);
|
|
400
|
+
otpIdRef.current = null;
|
|
401
|
+
// Connect wallet after successful authentication
|
|
402
|
+
await walletInstance.connect();
|
|
403
|
+
// Clear any previous errors on successful verification
|
|
404
|
+
setError(null);
|
|
405
|
+
return { success: true, user };
|
|
406
|
+
}
|
|
407
|
+
catch (err) {
|
|
408
|
+
console.error("OTP Verify Error:", err);
|
|
409
|
+
// Return the actual error message from the API
|
|
410
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to verify OTP";
|
|
411
|
+
return { success: false, error: errorMessage };
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
onGoogleLogin: async () => {
|
|
415
|
+
if (!walletInstance)
|
|
416
|
+
throw new Error("Wallet not initialized");
|
|
417
|
+
const connectionTypeValue = "google";
|
|
418
|
+
setConnectionType(connectionTypeValue);
|
|
419
|
+
// Store in localStorage for restoration
|
|
420
|
+
try {
|
|
421
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
422
|
+
localStorage.setItem("abstraxn_oauth_pending", "google");
|
|
423
|
+
}
|
|
424
|
+
catch (e) {
|
|
425
|
+
// Ignore localStorage errors
|
|
426
|
+
}
|
|
427
|
+
// Clear existing query params before starting new flow
|
|
428
|
+
if (typeof window !== "undefined") {
|
|
429
|
+
const url = new URL(window.location.href);
|
|
430
|
+
// Remove auth params that might be present from previous attempts
|
|
431
|
+
url.searchParams.delete("code");
|
|
432
|
+
url.searchParams.delete("state");
|
|
433
|
+
url.searchParams.delete("error");
|
|
434
|
+
url.searchParams.delete("success");
|
|
435
|
+
url.searchParams.delete("accessToken");
|
|
436
|
+
url.searchParams.delete("provider");
|
|
437
|
+
url.searchParams.delete("authProvider");
|
|
438
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
439
|
+
}
|
|
440
|
+
const authManager = walletInstance.getAuthManager();
|
|
441
|
+
await authManager.loginWithGoogle();
|
|
442
|
+
},
|
|
443
|
+
onTwitterLogin: async () => {
|
|
444
|
+
if (!walletInstance)
|
|
445
|
+
throw new Error("Wallet not initialized");
|
|
446
|
+
const connectionTypeValue = "x";
|
|
447
|
+
setConnectionType(connectionTypeValue);
|
|
448
|
+
try {
|
|
449
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
450
|
+
localStorage.setItem("abstraxn_oauth_pending", "twitter");
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
// Ignore localStorage errors
|
|
454
|
+
}
|
|
455
|
+
// Clear existing query params before starting new flow
|
|
456
|
+
if (typeof window !== "undefined") {
|
|
457
|
+
const url = new URL(window.location.href);
|
|
458
|
+
// Remove auth params that might be present from previous attempts
|
|
459
|
+
url.searchParams.delete("code");
|
|
460
|
+
url.searchParams.delete("state");
|
|
461
|
+
url.searchParams.delete("error");
|
|
462
|
+
url.searchParams.delete("success");
|
|
463
|
+
url.searchParams.delete("accessToken");
|
|
464
|
+
url.searchParams.delete("provider");
|
|
465
|
+
url.searchParams.delete("authProvider");
|
|
466
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
467
|
+
}
|
|
468
|
+
const authManager = walletInstance.getAuthManager();
|
|
469
|
+
await authManager.loginWithTwitter();
|
|
470
|
+
},
|
|
471
|
+
onDiscordLogin: async () => {
|
|
472
|
+
if (!walletInstance)
|
|
473
|
+
throw new Error("Wallet not initialized");
|
|
474
|
+
const connectionTypeValue = "discord";
|
|
475
|
+
setConnectionType(connectionTypeValue);
|
|
476
|
+
try {
|
|
477
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
478
|
+
localStorage.setItem("abstraxn_oauth_pending", "discord");
|
|
479
|
+
}
|
|
480
|
+
catch (e) {
|
|
481
|
+
// Ignore localStorage errors
|
|
482
|
+
}
|
|
483
|
+
// Clear existing query params before starting new flow
|
|
484
|
+
if (typeof window !== "undefined") {
|
|
485
|
+
const url = new URL(window.location.href);
|
|
486
|
+
// Remove auth params that might be present from previous attempts
|
|
487
|
+
url.searchParams.delete("code");
|
|
488
|
+
url.searchParams.delete("state");
|
|
489
|
+
url.searchParams.delete("error");
|
|
490
|
+
url.searchParams.delete("success");
|
|
491
|
+
url.searchParams.delete("accessToken");
|
|
492
|
+
url.searchParams.delete("provider");
|
|
493
|
+
url.searchParams.delete("authProvider");
|
|
494
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
495
|
+
}
|
|
496
|
+
const authManager = walletInstance.getAuthManager();
|
|
497
|
+
await authManager.loginWithDiscord();
|
|
498
|
+
},
|
|
499
|
+
onPasskeyLogin: async () => {
|
|
500
|
+
if (!walletInstance)
|
|
501
|
+
throw new Error("Wallet not initialized");
|
|
502
|
+
const connectionTypeValue = "passkey";
|
|
503
|
+
setConnectionType(connectionTypeValue);
|
|
504
|
+
try {
|
|
505
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
// Ignore localStorage errors
|
|
509
|
+
}
|
|
510
|
+
const authManager = walletInstance.getAuthManager();
|
|
511
|
+
const user = await authManager.loginWithPasskey();
|
|
512
|
+
// Set user immediately so onLoginSuccess can access it
|
|
513
|
+
setUser(user);
|
|
514
|
+
// Connect wallet after successful authentication
|
|
515
|
+
await walletInstance.connect();
|
|
516
|
+
},
|
|
517
|
+
onPasskeySignup: async () => {
|
|
518
|
+
if (!walletInstance)
|
|
519
|
+
throw new Error("Wallet not initialized");
|
|
520
|
+
const connectionTypeValue = "passkey";
|
|
521
|
+
setConnectionType(connectionTypeValue);
|
|
522
|
+
try {
|
|
523
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
524
|
+
}
|
|
525
|
+
catch (e) {
|
|
526
|
+
// Ignore localStorage errors
|
|
527
|
+
}
|
|
528
|
+
const authManager = walletInstance.getAuthManager();
|
|
529
|
+
const user = await authManager.signupWithPasskey();
|
|
530
|
+
// Clear any previous errors on successful verification
|
|
531
|
+
// setError(null);
|
|
532
|
+
setUser(user);
|
|
533
|
+
// Connect wallet after successful authentication
|
|
534
|
+
await walletInstance.connect();
|
|
535
|
+
},
|
|
536
|
+
onLoginSuccess: async (_data) => {
|
|
537
|
+
// Clear any previous errors on successful login
|
|
538
|
+
setError(null);
|
|
539
|
+
// Restore connection type from localStorage for OAuth logins (Google, X, Discord)
|
|
540
|
+
// This ensures connectionType is set after OAuth redirects
|
|
541
|
+
try {
|
|
542
|
+
const storedType = localStorage.getItem("abstraxn_connection_type");
|
|
543
|
+
if (storedType &&
|
|
544
|
+
(storedType === "google" ||
|
|
545
|
+
storedType === "x" ||
|
|
546
|
+
storedType === "discord" ||
|
|
547
|
+
storedType === "email" ||
|
|
548
|
+
storedType === "passkey")) {
|
|
549
|
+
setConnectionType(storedType);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch (e) {
|
|
553
|
+
// Ignore localStorage errors
|
|
554
|
+
}
|
|
555
|
+
// Load whoami after successful login
|
|
556
|
+
if (walletInstance) {
|
|
557
|
+
try {
|
|
558
|
+
const whoamiInfo = await walletInstance.getWhoami();
|
|
559
|
+
setWhoami(whoamiInfo);
|
|
560
|
+
const addr = await walletInstance.getAddress();
|
|
561
|
+
setAddress(addr);
|
|
562
|
+
const cid = await walletInstance.getChainId();
|
|
563
|
+
setChainId(cid);
|
|
564
|
+
// Only hide onboarding UI and set connected if whoami succeeds
|
|
565
|
+
if (onboardingRef.current) {
|
|
566
|
+
const onboardingAny = onboardingRef.current;
|
|
567
|
+
if (onboardingAny.hideLoadingModal) {
|
|
568
|
+
onboardingAny.hideLoadingModal();
|
|
569
|
+
}
|
|
570
|
+
if (onboardingAny.modalOverlay) {
|
|
571
|
+
onboardingAny.modalOverlay.classList.remove("onboarding-modal-open");
|
|
572
|
+
onboardingAny.modalOverlay.classList.add("onboarding-modal-closing");
|
|
573
|
+
setTimeout(() => {
|
|
574
|
+
if (onboardingAny.modalOverlay) {
|
|
575
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
576
|
+
}
|
|
577
|
+
}, 200);
|
|
578
|
+
}
|
|
579
|
+
document.body.style.overflow = "";
|
|
580
|
+
}
|
|
581
|
+
// Set connected state only after whoami succeeds
|
|
582
|
+
setIsConnected(true);
|
|
583
|
+
}
|
|
584
|
+
catch (err) {
|
|
585
|
+
console.error("Error loading whoami after login:", err);
|
|
586
|
+
// Hide loading modal on whoami error
|
|
587
|
+
if (onboardingRef.current) {
|
|
588
|
+
const onboardingAny = onboardingRef.current;
|
|
589
|
+
if (onboardingAny.hideLoadingModal) {
|
|
590
|
+
onboardingAny.hideLoadingModal();
|
|
591
|
+
}
|
|
592
|
+
// Show error modal/screen
|
|
593
|
+
const errorMessage = err instanceof Error
|
|
594
|
+
? err.message
|
|
595
|
+
: "Failed to load user information";
|
|
596
|
+
if (onboardingAny.showError) {
|
|
597
|
+
onboardingAny.showError(errorMessage);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// Set error state
|
|
601
|
+
const error = err instanceof Error
|
|
602
|
+
? err
|
|
603
|
+
: new Error("Failed to load user information after login");
|
|
604
|
+
setError(error);
|
|
605
|
+
// Don't set connected if whoami fails
|
|
606
|
+
setIsConnected(false);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
onLoginError: (err) => {
|
|
611
|
+
setError(err);
|
|
612
|
+
},
|
|
613
|
+
}, externalWalletsEnabled); // Pass externalWalletsEnabled as second parameter
|
|
614
|
+
// Inject custom CSS if provided
|
|
615
|
+
if (uiConfig.customCSS) {
|
|
616
|
+
const styleId = "abstraxn-custom-css";
|
|
617
|
+
if (!document.getElementById(styleId)) {
|
|
618
|
+
const style = document.createElement("style");
|
|
619
|
+
style.id = styleId;
|
|
620
|
+
style.textContent = uiConfig.customCSS;
|
|
621
|
+
document.head.appendChild(style);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const hasAuthParams = (params) => params.get("success") === "true" ||
|
|
625
|
+
params.get("error") ||
|
|
626
|
+
params.get("code") ||
|
|
627
|
+
params.get("accessToken");
|
|
628
|
+
const getAuthProviderFromToken = (token) => {
|
|
629
|
+
try {
|
|
630
|
+
const base64Url = token.split(".")[1];
|
|
631
|
+
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
632
|
+
const jsonPayload = decodeURIComponent(window
|
|
633
|
+
.atob(base64)
|
|
634
|
+
.split("")
|
|
635
|
+
.map(function (c) {
|
|
636
|
+
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
|
637
|
+
})
|
|
638
|
+
.join(""));
|
|
639
|
+
const payload = JSON.parse(jsonPayload);
|
|
640
|
+
return (payload.authProvider || payload.provider || "").toLowerCase();
|
|
641
|
+
}
|
|
642
|
+
catch (e) {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
const matchesProvider = (provider, params) => {
|
|
647
|
+
const providerParam = (params.get("provider") ||
|
|
648
|
+
params.get("authProvider") ||
|
|
649
|
+
"").toLowerCase();
|
|
650
|
+
const path = window.location.pathname.toLowerCase();
|
|
651
|
+
if (providerParam) {
|
|
652
|
+
if (provider === "twitter") {
|
|
653
|
+
return providerParam === "twitter" || providerParam === "x";
|
|
654
|
+
}
|
|
655
|
+
return providerParam === provider;
|
|
656
|
+
}
|
|
657
|
+
// Check accessToken for provider
|
|
658
|
+
const accessToken = params.get("accessToken");
|
|
659
|
+
if (accessToken) {
|
|
660
|
+
const tokenProvider = getAuthProviderFromToken(accessToken);
|
|
661
|
+
if (tokenProvider) {
|
|
662
|
+
if (provider === "twitter") {
|
|
663
|
+
return tokenProvider === "twitter" || tokenProvider === "x";
|
|
664
|
+
}
|
|
665
|
+
return tokenProvider === provider;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (provider === "twitter") {
|
|
669
|
+
return path.includes("/twitter") || path.includes("/x");
|
|
670
|
+
}
|
|
671
|
+
return path.includes(`/${provider}`);
|
|
672
|
+
};
|
|
673
|
+
try {
|
|
674
|
+
onboarding.init();
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
console.error("Failed to initialize OnboardingUI:", err);
|
|
678
|
+
}
|
|
679
|
+
// Check if we should keep it open (if handling a callback)
|
|
680
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
681
|
+
const shouldKeepOpen = hasAuthParams(urlParams);
|
|
682
|
+
const hasSuccess = urlParams.get("success") === "true";
|
|
683
|
+
// Hide immediately - use synchronous approach to prevent flicker
|
|
684
|
+
// Only hide if NOT handling a callback
|
|
685
|
+
const onboardingAny = onboarding;
|
|
686
|
+
if (!shouldKeepOpen && onboardingAny.modalOverlay) {
|
|
687
|
+
// Hide immediately without delay to prevent flicker
|
|
688
|
+
onboardingAny.modalOverlay.classList.remove("onboarding-modal-open");
|
|
689
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
690
|
+
document.body.style.overflow = "";
|
|
691
|
+
}
|
|
692
|
+
else if (hasSuccess) {
|
|
693
|
+
// If success=true is in URL, show loading modal
|
|
694
|
+
// Use setTimeout to ensure onboarding is fully initialized
|
|
695
|
+
setTimeout(() => {
|
|
696
|
+
const onboardingInstance = onboardingRef.current;
|
|
697
|
+
if (onboardingInstance && onboardingInstance.showLoadingModal) {
|
|
698
|
+
// Directly show loading modal - it creates its own overlay with header and footer
|
|
699
|
+
onboardingInstance.showLoadingModal();
|
|
700
|
+
}
|
|
701
|
+
}, 150);
|
|
702
|
+
}
|
|
703
|
+
onboardingRef.current = onboarding;
|
|
704
|
+
// Handle Google OAuth callback (only in useEffect, not exposed)
|
|
705
|
+
const handleGoogleCallback = async (force = false) => {
|
|
706
|
+
if (googleCallbackHandledRef.current)
|
|
707
|
+
return;
|
|
708
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
709
|
+
if (!hasAuthParams(urlParams) ||
|
|
710
|
+
matchesProvider("twitter", urlParams) ||
|
|
711
|
+
matchesProvider("discord", urlParams)) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
// Check if we initiated this OAuth flow
|
|
715
|
+
let oauthPending = null;
|
|
716
|
+
try {
|
|
717
|
+
oauthPending = localStorage.getItem("abstraxn_oauth_pending");
|
|
718
|
+
}
|
|
719
|
+
catch (e) { }
|
|
720
|
+
if (!force && !oauthPending)
|
|
721
|
+
return;
|
|
722
|
+
// Clear the pending flag
|
|
723
|
+
try {
|
|
724
|
+
localStorage.removeItem("abstraxn_oauth_pending");
|
|
725
|
+
}
|
|
726
|
+
catch (e) { }
|
|
727
|
+
// Show loading modal if success=true is in URL
|
|
728
|
+
const hasSuccess = urlParams.get("success") === "true";
|
|
729
|
+
if (hasSuccess && onboardingRef.current) {
|
|
730
|
+
const onboardingAny = onboardingRef.current;
|
|
731
|
+
if (onboardingAny.showLoadingScreen) {
|
|
732
|
+
onboardingAny.showLoadingScreen();
|
|
733
|
+
}
|
|
734
|
+
else if (onboardingAny.showLoadingModal) {
|
|
735
|
+
onboardingAny.showLoadingModal();
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
googleCallbackHandledRef.current = true;
|
|
739
|
+
try {
|
|
740
|
+
const user = await walletInstance.handleGoogleCallback();
|
|
741
|
+
if (user) {
|
|
742
|
+
setUser(user);
|
|
743
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
744
|
+
// Hide loading modal and close modal on success
|
|
745
|
+
if (onboardingRef.current) {
|
|
746
|
+
const onboardingAny = onboardingRef.current;
|
|
747
|
+
if (onboardingAny.hideLoadingModal) {
|
|
748
|
+
onboardingAny.hideLoadingModal();
|
|
749
|
+
}
|
|
750
|
+
onboardingRef.current.close();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
console.error("Google callback error:", err);
|
|
756
|
+
// Hide loading modal on error (including whoami API failures)
|
|
757
|
+
if (onboardingRef.current) {
|
|
758
|
+
const onboardingAny = onboardingRef.current;
|
|
759
|
+
// Always try to hide loading modal first
|
|
760
|
+
if (onboardingAny.hideLoadingModal) {
|
|
761
|
+
onboardingAny.hideLoadingModal();
|
|
762
|
+
}
|
|
763
|
+
// Also hide any main modal overlay
|
|
764
|
+
if (onboardingAny.modalOverlay) {
|
|
765
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
766
|
+
}
|
|
767
|
+
// Show error modal/screen
|
|
768
|
+
const errorMessage = err instanceof Error ? err.message : "Google authentication failed";
|
|
769
|
+
if (onboardingAny.showError) {
|
|
770
|
+
onboardingAny.showError(errorMessage);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
setError(err instanceof Error ? err : new Error("Google callback failed"));
|
|
774
|
+
googleCallbackHandledRef.current = false;
|
|
775
|
+
// Ensure body scroll is restored
|
|
776
|
+
document.body.style.overflow = "";
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
const handleDiscordCallback = async () => {
|
|
780
|
+
if (discordCallbackHandledRef.current)
|
|
781
|
+
return;
|
|
782
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
783
|
+
if (!hasAuthParams(urlParams) || !matchesProvider("discord", urlParams)) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
// Check if we initiated this OAuth flow
|
|
787
|
+
let oauthPending = null;
|
|
788
|
+
try {
|
|
789
|
+
oauthPending = localStorage.getItem("abstraxn_oauth_pending");
|
|
790
|
+
}
|
|
791
|
+
catch (e) { }
|
|
792
|
+
if (!oauthPending)
|
|
793
|
+
return;
|
|
794
|
+
// Clear the pending flag
|
|
795
|
+
try {
|
|
796
|
+
localStorage.removeItem("abstraxn_oauth_pending");
|
|
797
|
+
}
|
|
798
|
+
catch (e) { }
|
|
799
|
+
// Show loading modal if success=true is in URL
|
|
800
|
+
const hasSuccess = urlParams.get("success") === "true";
|
|
801
|
+
if (hasSuccess && onboardingRef.current) {
|
|
802
|
+
const onboardingAny = onboardingRef.current;
|
|
803
|
+
if (onboardingAny.showLoadingScreen) {
|
|
804
|
+
onboardingAny.showLoadingScreen();
|
|
805
|
+
}
|
|
806
|
+
else if (onboardingAny.showLoadingModal) {
|
|
807
|
+
onboardingAny.showLoadingModal();
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
discordCallbackHandledRef.current = true;
|
|
811
|
+
try {
|
|
812
|
+
const user = await walletInstance.handleDiscordCallback();
|
|
813
|
+
if (user) {
|
|
814
|
+
setUser(user);
|
|
815
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
816
|
+
// Hide loading modal and close modal on success
|
|
817
|
+
if (onboardingRef.current) {
|
|
818
|
+
const onboardingAny = onboardingRef.current;
|
|
819
|
+
if (onboardingAny.hideLoadingModal) {
|
|
820
|
+
onboardingAny.hideLoadingModal();
|
|
821
|
+
}
|
|
822
|
+
onboardingRef.current.close();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
catch (err) {
|
|
827
|
+
console.error("Discord callback error:", err);
|
|
828
|
+
// Hide loading modal on error (including whoami API failures)
|
|
829
|
+
if (onboardingRef.current) {
|
|
830
|
+
const onboardingAny = onboardingRef.current;
|
|
831
|
+
// Always try to hide loading modal first
|
|
832
|
+
if (onboardingAny.hideLoadingModal) {
|
|
833
|
+
onboardingAny.hideLoadingModal();
|
|
834
|
+
}
|
|
835
|
+
// Also hide any main modal overlay
|
|
836
|
+
if (onboardingAny.modalOverlay) {
|
|
837
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
838
|
+
}
|
|
839
|
+
// Show error modal/screen
|
|
840
|
+
const errorMessage = err instanceof Error
|
|
841
|
+
? err.message
|
|
842
|
+
: "Discord authentication failed";
|
|
843
|
+
if (onboardingAny.showError) {
|
|
844
|
+
onboardingAny.showError(errorMessage);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
setError(err instanceof Error ? err : new Error("Discord callback failed"));
|
|
848
|
+
discordCallbackHandledRef.current = false;
|
|
849
|
+
// Ensure body scroll is restored
|
|
850
|
+
document.body.style.overflow = "";
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
const handleTwitterCallback = async () => {
|
|
854
|
+
if (twitterCallbackHandledRef.current)
|
|
855
|
+
return;
|
|
856
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
857
|
+
if (!hasAuthParams(urlParams) || !matchesProvider("twitter", urlParams)) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
// Check if we initiated this OAuth flow
|
|
861
|
+
let oauthPending = null;
|
|
862
|
+
try {
|
|
863
|
+
oauthPending = localStorage.getItem("abstraxn_oauth_pending");
|
|
864
|
+
}
|
|
865
|
+
catch (e) { }
|
|
866
|
+
if (!oauthPending)
|
|
867
|
+
return;
|
|
868
|
+
// Clear the pending flag
|
|
869
|
+
try {
|
|
870
|
+
localStorage.removeItem("abstraxn_oauth_pending");
|
|
871
|
+
}
|
|
872
|
+
catch (e) { }
|
|
873
|
+
// Show loading modal if success=true is in URL
|
|
874
|
+
const hasSuccess = urlParams.get("success") === "true";
|
|
875
|
+
if (hasSuccess && onboardingRef.current) {
|
|
876
|
+
const onboardingAny = onboardingRef.current;
|
|
877
|
+
if (onboardingAny.showLoadingScreen) {
|
|
878
|
+
onboardingAny.showLoadingScreen();
|
|
879
|
+
}
|
|
880
|
+
else if (onboardingAny.showLoadingModal) {
|
|
881
|
+
onboardingAny.showLoadingModal();
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
twitterCallbackHandledRef.current = true;
|
|
885
|
+
try {
|
|
886
|
+
const user = await walletInstance.handleTwitterCallback();
|
|
887
|
+
if (user) {
|
|
888
|
+
setUser(user);
|
|
889
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
890
|
+
// Hide loading modal and close modal on success
|
|
891
|
+
if (onboardingRef.current) {
|
|
892
|
+
const onboardingAny = onboardingRef.current;
|
|
893
|
+
if (onboardingAny.hideLoadingModal) {
|
|
894
|
+
onboardingAny.hideLoadingModal();
|
|
895
|
+
}
|
|
896
|
+
onboardingRef.current.close();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
catch (err) {
|
|
901
|
+
console.error("Twitter callback error:", err);
|
|
902
|
+
// Hide loading modal on error (including whoami API failures)
|
|
903
|
+
if (onboardingRef.current) {
|
|
904
|
+
const onboardingAny = onboardingRef.current;
|
|
905
|
+
// Always try to hide loading modal first
|
|
906
|
+
if (onboardingAny.hideLoadingModal) {
|
|
907
|
+
onboardingAny.hideLoadingModal();
|
|
908
|
+
}
|
|
909
|
+
// Also hide any main modal overlay
|
|
910
|
+
if (onboardingAny.modalOverlay) {
|
|
911
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
912
|
+
}
|
|
913
|
+
// Show error modal/screen
|
|
914
|
+
const errorMessage = err instanceof Error
|
|
915
|
+
? err.message
|
|
916
|
+
: "Twitter authentication failed";
|
|
917
|
+
if (onboardingAny.showError) {
|
|
918
|
+
onboardingAny.showError(errorMessage);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
setError(err instanceof Error ? err : new Error("Twitter callback failed"));
|
|
922
|
+
twitterCallbackHandledRef.current = false;
|
|
923
|
+
// Ensure body scroll is restored
|
|
924
|
+
document.body.style.overflow = "";
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
handleGoogleCallback();
|
|
928
|
+
handleDiscordCallback();
|
|
929
|
+
handleTwitterCallback();
|
|
930
|
+
// Helper function to restore connection state
|
|
931
|
+
const restoreConnectionState = async (wallet) => {
|
|
932
|
+
try {
|
|
933
|
+
// Verify whoami exists before setting connected
|
|
934
|
+
const whoamiInfo = await wallet.getWhoami();
|
|
935
|
+
if (!whoamiInfo) {
|
|
936
|
+
// If whoami is not available, don't set connected
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
setIsConnected(true);
|
|
940
|
+
const userInfo = await wallet.getUserInfo();
|
|
941
|
+
setUser(userInfo);
|
|
942
|
+
setWhoami(whoamiInfo);
|
|
943
|
+
// Try to get address, but don't fail if not available yet
|
|
944
|
+
try {
|
|
945
|
+
const addr = await wallet.getAddress();
|
|
946
|
+
setAddress(addr);
|
|
947
|
+
}
|
|
948
|
+
catch (addrErr) {
|
|
949
|
+
console.warn("Address not available during restore:", addrErr);
|
|
950
|
+
}
|
|
951
|
+
const cid = await wallet.getChainId();
|
|
952
|
+
setChainId(cid);
|
|
953
|
+
}
|
|
954
|
+
catch (err) {
|
|
955
|
+
console.error("Error restoring connection state:", err);
|
|
956
|
+
// If restore fails, mark as disconnected
|
|
957
|
+
setIsConnected(false);
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
// Auto-initialize if enabled
|
|
961
|
+
if (config.autoInit !== false) {
|
|
962
|
+
init().then(() => {
|
|
963
|
+
// After initialization, check if wallet is already connected (from localStorage)
|
|
964
|
+
// The wallet's initialize() method already sets isConnected if authenticated
|
|
965
|
+
// But we need to restore React state if the event was missed
|
|
966
|
+
if (walletInstance && walletInstance.isConnected) {
|
|
967
|
+
// Wallet is already connected, restore state
|
|
968
|
+
restoreConnectionState(walletInstance);
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
return () => {
|
|
973
|
+
if (onboardingRef.current) {
|
|
974
|
+
onboardingRef.current.destroy();
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
}, []); // Only run once on mount
|
|
978
|
+
// Ref to store the root instance to prevent re-mounting (defined early)
|
|
979
|
+
const externalWalletRootRef = useRef(null);
|
|
980
|
+
const externalWalletMountedRef = useRef(false);
|
|
981
|
+
const externalWalletContainerRef = useRef(null);
|
|
982
|
+
// Show onboarding UI (defined first to avoid hoisting issues)
|
|
983
|
+
const showOnboarding = useCallback(() => {
|
|
984
|
+
setError(null); // Clear any previous errors
|
|
985
|
+
if (!onboardingRef.current) {
|
|
986
|
+
console.error("OnboardingUI not initialized - cannot show modal");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (onboardingRef.current) {
|
|
990
|
+
const onboarding = onboardingRef.current;
|
|
991
|
+
// Re-initialize if destroyed
|
|
992
|
+
if (!onboarding.modalOverlay ||
|
|
993
|
+
!document.body.contains(onboarding.modalOverlay)) {
|
|
994
|
+
onboarding.init();
|
|
995
|
+
// Wait for init to complete
|
|
996
|
+
setTimeout(() => {
|
|
997
|
+
if (onboarding.modalOverlay) {
|
|
998
|
+
// Use requestAnimationFrame to prevent blinking
|
|
999
|
+
requestAnimationFrame(() => {
|
|
1000
|
+
onboarding.modalOverlay.classList.remove("onboarding-modal-closing");
|
|
1001
|
+
onboarding.modalOverlay.classList.add("onboarding-modal-open");
|
|
1002
|
+
onboarding.modalOverlay.style.display = "flex";
|
|
1003
|
+
document.body.style.overflow = "hidden";
|
|
1004
|
+
// Ensure external wallet container is visible when modal opens
|
|
1005
|
+
if (externalWalletsEnabledRef.current &&
|
|
1006
|
+
onboarding.externalWalletContainer) {
|
|
1007
|
+
// Check if we're on OTP screen - if so, don't show external wallets
|
|
1008
|
+
// Check if OTP screen exists AND is actually visible in the DOM
|
|
1009
|
+
const isOtpScreen = onboarding.otpVerificationScreen &&
|
|
1010
|
+
onboarding.otpVerificationScreen.parentElement &&
|
|
1011
|
+
onboarding.otpVerificationScreen.offsetParent !== null;
|
|
1012
|
+
if (!isOtpScreen) {
|
|
1013
|
+
// Only show external wallets if not on OTP screen
|
|
1014
|
+
onboarding.externalWalletContainer.style.display = "";
|
|
1015
|
+
// Show divider only if both email/Google AND external wallets are visible
|
|
1016
|
+
const authMethods = onboarding.config?.authMethods || [
|
|
1017
|
+
"otp",
|
|
1018
|
+
"google",
|
|
1019
|
+
];
|
|
1020
|
+
const showEmail = authMethods.includes("otp");
|
|
1021
|
+
const showGoogle = authMethods.includes("google");
|
|
1022
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
1023
|
+
if (onboarding.externalWalletDivider && hasEmailOrGoogle) {
|
|
1024
|
+
onboarding.externalWalletDivider.style.display = "";
|
|
1025
|
+
}
|
|
1026
|
+
else if (onboarding.externalWalletDivider) {
|
|
1027
|
+
onboarding.externalWalletDivider.style.display = "none";
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
// On OTP screen - hide external wallets
|
|
1032
|
+
onboarding.externalWalletContainer.style.display = "none";
|
|
1033
|
+
if (onboarding.externalWalletDivider) {
|
|
1034
|
+
onboarding.externalWalletDivider.style.display = "none";
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
// Always re-render external wallets when modal opens immediately
|
|
1038
|
+
setTimeout(() => {
|
|
1039
|
+
const container = onboarding.externalWalletContainer;
|
|
1040
|
+
if (!container) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
// Use the reusable mount function
|
|
1044
|
+
mountExternalWalletsToContainer(container, onboarding);
|
|
1045
|
+
}, 50); // Reduced from 200ms to 50ms
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}, 50);
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
// Modal already exists, just show it
|
|
1053
|
+
// Use requestAnimationFrame to prevent blinking
|
|
1054
|
+
requestAnimationFrame(() => {
|
|
1055
|
+
// Check if we're on OTP screen - if so, ensure social buttons stay hidden
|
|
1056
|
+
const isOtpScreen = onboarding.otpVerificationScreen &&
|
|
1057
|
+
onboarding.otpVerificationScreen.parentElement &&
|
|
1058
|
+
onboarding.otpVerificationScreen.offsetParent !== null;
|
|
1059
|
+
onboarding.modalOverlay.classList.remove("onboarding-modal-closing");
|
|
1060
|
+
onboarding.modalOverlay.classList.add("onboarding-modal-open");
|
|
1061
|
+
onboarding.modalOverlay.style.display = "flex";
|
|
1062
|
+
if (onboarding.rootElement) {
|
|
1063
|
+
onboarding.rootElement.style.display = "";
|
|
1064
|
+
}
|
|
1065
|
+
document.body.style.overflow = "hidden";
|
|
1066
|
+
// If on OTP screen, ensure all login elements (social buttons, etc.) are hidden
|
|
1067
|
+
if (isOtpScreen) {
|
|
1068
|
+
// On OTP screen - ensure all login elements are hidden
|
|
1069
|
+
if (onboarding.hideLoginElements &&
|
|
1070
|
+
typeof onboarding.hideLoginElements === "function") {
|
|
1071
|
+
onboarding.hideLoginElements();
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
// Fallback: manually hide elements
|
|
1075
|
+
if (onboarding.googleButton)
|
|
1076
|
+
onboarding.googleButton.style.display = "none";
|
|
1077
|
+
if (onboarding.twitterButton)
|
|
1078
|
+
onboarding.twitterButton.style.display = "none";
|
|
1079
|
+
if (onboarding.discordButton)
|
|
1080
|
+
onboarding.discordButton.style.display = "none";
|
|
1081
|
+
if (onboarding.socialGrid)
|
|
1082
|
+
onboarding.socialGrid.style.display = "none";
|
|
1083
|
+
if (onboarding.divider)
|
|
1084
|
+
onboarding.divider.style.display = "none";
|
|
1085
|
+
if (onboarding.passkeyLoginButton)
|
|
1086
|
+
onboarding.passkeyLoginButton.style.display = "none";
|
|
1087
|
+
if (onboarding.passkeySignupLink)
|
|
1088
|
+
onboarding.passkeySignupLink.style.display = "none";
|
|
1089
|
+
if (onboarding.passkeyDivider)
|
|
1090
|
+
onboarding.passkeyDivider.style.display = "none";
|
|
1091
|
+
if (onboarding.externalWalletContainer)
|
|
1092
|
+
onboarding.externalWalletContainer.style.display = "none";
|
|
1093
|
+
if (onboarding.externalWalletDivider)
|
|
1094
|
+
onboarding.externalWalletDivider.style.display = "none";
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
// CRITICAL: Always ensure external wallets are mounted when modal reopens
|
|
1098
|
+
if (externalWalletsEnabledRef.current &&
|
|
1099
|
+
onboarding.externalWalletContainer) {
|
|
1100
|
+
// Check if we're on OTP screen - if so, don't show external wallets
|
|
1101
|
+
if (!isOtpScreen) {
|
|
1102
|
+
// Only show external wallets if not on OTP screen
|
|
1103
|
+
onboarding.externalWalletContainer.style.display = "";
|
|
1104
|
+
// Show divider only if both email/Google AND external wallets are visible
|
|
1105
|
+
const authMethods = onboarding.config?.authMethods || [
|
|
1106
|
+
"otp",
|
|
1107
|
+
"google",
|
|
1108
|
+
];
|
|
1109
|
+
const showEmail = authMethods.includes("otp");
|
|
1110
|
+
const showGoogle = authMethods.includes("google");
|
|
1111
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
1112
|
+
if (onboarding.externalWalletDivider && hasEmailOrGoogle) {
|
|
1113
|
+
onboarding.externalWalletDivider.style.display = "";
|
|
1114
|
+
}
|
|
1115
|
+
else if (onboarding.externalWalletDivider) {
|
|
1116
|
+
onboarding.externalWalletDivider.style.display = "none";
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
// On OTP screen - hide external wallets
|
|
1121
|
+
onboarding.externalWalletContainer.style.display = "none";
|
|
1122
|
+
if (onboarding.externalWalletDivider) {
|
|
1123
|
+
onboarding.externalWalletDivider.style.display = "none";
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// Always re-render external wallets when modal opens (even if already mounted)
|
|
1127
|
+
// This ensures they're visible after modal close/reopen
|
|
1128
|
+
setTimeout(() => {
|
|
1129
|
+
const container = onboarding.externalWalletContainer;
|
|
1130
|
+
if (!container) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
// Use the reusable mount function (will be defined later, but accessible via closure)
|
|
1134
|
+
if (typeof mountExternalWalletsToContainer === "function") {
|
|
1135
|
+
mountExternalWalletsToContainer(container, onboarding);
|
|
1136
|
+
}
|
|
1137
|
+
}, 50); // Reduced delay for faster initial render
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
// Note: externalWalletsEnabled is accessed via ref to keep this callback stable
|
|
1143
|
+
}, []);
|
|
1144
|
+
// Initialize IndexedDB
|
|
1145
|
+
const init = useCallback(async () => {
|
|
1146
|
+
if (!walletRef.current || isInitialized)
|
|
1147
|
+
return;
|
|
1148
|
+
setLoading(true);
|
|
1149
|
+
setError(null);
|
|
1150
|
+
try {
|
|
1151
|
+
const authManager = walletRef.current.getAuthManager();
|
|
1152
|
+
const turnkeyService = authManager.turnkeyService;
|
|
1153
|
+
await turnkeyService.init();
|
|
1154
|
+
setIsInitialized(true);
|
|
1155
|
+
// Try to connect wallet to check if user is already authenticated (from localStorage)
|
|
1156
|
+
// This will trigger the wallet's initialize() which checks localStorage
|
|
1157
|
+
try {
|
|
1158
|
+
await walletRef.current.connect();
|
|
1159
|
+
// If connect succeeds, the 'connect' event will be emitted
|
|
1160
|
+
// But also manually restore state in case event was missed
|
|
1161
|
+
if (walletRef.current && walletRef.current.isConnected) {
|
|
1162
|
+
const addr = await walletRef.current.getAddress();
|
|
1163
|
+
const cid = await walletRef.current.getChainId();
|
|
1164
|
+
const userInfo = await walletRef.current.getUserInfo();
|
|
1165
|
+
const whoamiInfo = await walletRef.current.getWhoami();
|
|
1166
|
+
setAddress(addr);
|
|
1167
|
+
setChainId(cid);
|
|
1168
|
+
setUser(userInfo);
|
|
1169
|
+
setWhoami(whoamiInfo);
|
|
1170
|
+
setIsConnected(true);
|
|
1171
|
+
// Restore connection type from localStorage
|
|
1172
|
+
try {
|
|
1173
|
+
const storedType = localStorage.getItem("abstraxn_connection_type");
|
|
1174
|
+
if (storedType &&
|
|
1175
|
+
(storedType === "google" ||
|
|
1176
|
+
storedType === "email" ||
|
|
1177
|
+
storedType === "discord" ||
|
|
1178
|
+
storedType === "x" ||
|
|
1179
|
+
storedType === "passkey")) {
|
|
1180
|
+
setConnectionType(storedType);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
catch (e) {
|
|
1184
|
+
// Ignore localStorage errors
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
catch (err) {
|
|
1189
|
+
// If connect fails (user not authenticated), that's fine
|
|
1190
|
+
// Just don't set connected state
|
|
1191
|
+
if (walletRef.current && walletRef.current.isConnected) {
|
|
1192
|
+
// Wallet says it's connected, restore state
|
|
1193
|
+
try {
|
|
1194
|
+
// Verify whoami exists before setting connected
|
|
1195
|
+
const whoamiInfo = await walletRef.current.getWhoami();
|
|
1196
|
+
if (!whoamiInfo) {
|
|
1197
|
+
// If whoami is not available, don't set connected
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
setIsConnected(true);
|
|
1201
|
+
const userInfo = await walletRef.current.getUserInfo();
|
|
1202
|
+
setUser(userInfo);
|
|
1203
|
+
setWhoami(whoamiInfo);
|
|
1204
|
+
// Try to get address, but don't fail if not available yet
|
|
1205
|
+
try {
|
|
1206
|
+
const addr = await walletRef.current.getAddress();
|
|
1207
|
+
setAddress(addr);
|
|
1208
|
+
}
|
|
1209
|
+
catch (addrErr) {
|
|
1210
|
+
console.warn("Address not available during restore:", addrErr);
|
|
1211
|
+
}
|
|
1212
|
+
const cid = await walletRef.current.getChainId();
|
|
1213
|
+
setChainId(cid);
|
|
1214
|
+
// Restore connection type from localStorage
|
|
1215
|
+
try {
|
|
1216
|
+
const storedType = localStorage.getItem("abstraxn_connection_type");
|
|
1217
|
+
if (storedType &&
|
|
1218
|
+
(storedType === "google" ||
|
|
1219
|
+
storedType === "email" ||
|
|
1220
|
+
storedType === "discord" ||
|
|
1221
|
+
storedType === "x" ||
|
|
1222
|
+
storedType === "passkey")) {
|
|
1223
|
+
setConnectionType(storedType);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
catch (e) {
|
|
1227
|
+
// Ignore localStorage errors
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
catch (restoreErr) {
|
|
1231
|
+
console.error("Error restoring connection state:", restoreErr);
|
|
1232
|
+
setIsConnected(false);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
catch (err) {
|
|
1238
|
+
const error = err instanceof Error ? err : new Error("Failed to initialize");
|
|
1239
|
+
setError(error);
|
|
1240
|
+
console.error("Init error:", err);
|
|
1241
|
+
}
|
|
1242
|
+
finally {
|
|
1243
|
+
setLoading(false);
|
|
1244
|
+
}
|
|
1245
|
+
}, [isInitialized]);
|
|
1246
|
+
// Connect wallet
|
|
1247
|
+
const connect = useCallback(async () => {
|
|
1248
|
+
if (!walletRef.current)
|
|
1249
|
+
return;
|
|
1250
|
+
setLoading(true);
|
|
1251
|
+
setError(null);
|
|
1252
|
+
try {
|
|
1253
|
+
await walletRef.current.connect();
|
|
1254
|
+
setIsConnected(walletRef.current.isConnected);
|
|
1255
|
+
// Load whoami after connect
|
|
1256
|
+
try {
|
|
1257
|
+
const whoamiInfo = await walletRef.current.getWhoami();
|
|
1258
|
+
setWhoami(whoamiInfo);
|
|
1259
|
+
const addr = await walletRef.current.getAddress();
|
|
1260
|
+
setAddress(addr);
|
|
1261
|
+
}
|
|
1262
|
+
catch (err) {
|
|
1263
|
+
console.error("Error loading whoami after connect:", err);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
catch (err) {
|
|
1267
|
+
if (err instanceof AuthenticationError) {
|
|
1268
|
+
// Show onboarding UI
|
|
1269
|
+
showOnboarding();
|
|
1270
|
+
setError(err);
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
setError(err instanceof Error ? err : new Error("Failed to connect"));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
finally {
|
|
1277
|
+
setLoading(false);
|
|
1278
|
+
}
|
|
1279
|
+
}, [showOnboarding]);
|
|
1280
|
+
// Disconnect wallet
|
|
1281
|
+
const disconnect = useCallback(async () => {
|
|
1282
|
+
setLoading(true);
|
|
1283
|
+
setError(null);
|
|
1284
|
+
disconnectingRef.current = true; // Set flag to prevent useEffect from interfering
|
|
1285
|
+
try {
|
|
1286
|
+
const wasExternalWalletConnected = isExternalWalletConnected;
|
|
1287
|
+
const wasAbstraxnWalletConnected = walletRef.current?.isConnected ?? false;
|
|
1288
|
+
// If external wallet is connected, disconnect it first
|
|
1289
|
+
if (wasExternalWalletConnected &&
|
|
1290
|
+
externalWalletsEnabled &&
|
|
1291
|
+
wagmiDisconnect) {
|
|
1292
|
+
try {
|
|
1293
|
+
await wagmiDisconnect.disconnect();
|
|
1294
|
+
// Wait a bit to ensure wagmi state updates
|
|
1295
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1296
|
+
// Reset external wallet state immediately
|
|
1297
|
+
setIsExternalWalletConnected(false);
|
|
1298
|
+
setExternalWalletAddress(null);
|
|
1299
|
+
setExternalWalletChainId(null);
|
|
1300
|
+
setConnectionType(null);
|
|
1301
|
+
// Clear from localStorage
|
|
1302
|
+
try {
|
|
1303
|
+
localStorage.removeItem("abstraxn_connection_type");
|
|
1304
|
+
}
|
|
1305
|
+
catch (e) {
|
|
1306
|
+
// Ignore localStorage errors
|
|
1307
|
+
}
|
|
1308
|
+
explicitConnectionRef.current = false;
|
|
1309
|
+
lastConnectionTimeRef.current = 0;
|
|
1310
|
+
// Always reset main isConnected when disconnecting external wallet
|
|
1311
|
+
// Since the context value is computed as isExternalWalletConnected || isConnected,
|
|
1312
|
+
// we need to set isConnected to false so the overall isConnected becomes false
|
|
1313
|
+
setIsConnected(false);
|
|
1314
|
+
setAddress(null);
|
|
1315
|
+
setChainId(null);
|
|
1316
|
+
}
|
|
1317
|
+
catch (err) {
|
|
1318
|
+
throw err;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
// Disconnect Abstraxn wallet if it's connected
|
|
1322
|
+
if (wasAbstraxnWalletConnected && walletRef.current) {
|
|
1323
|
+
// console.log('Disconnecting Abstraxn wallet...');
|
|
1324
|
+
try {
|
|
1325
|
+
await walletRef.current.disconnect();
|
|
1326
|
+
// After disconnecting Abstraxn wallet, always reset main state
|
|
1327
|
+
// The context value will be computed correctly based on both wallet states
|
|
1328
|
+
setIsConnected(false);
|
|
1329
|
+
setAddress(null);
|
|
1330
|
+
setChainId(null);
|
|
1331
|
+
setConnectionType(null);
|
|
1332
|
+
// Clear from localStorage
|
|
1333
|
+
try {
|
|
1334
|
+
localStorage.removeItem("abstraxn_connection_type");
|
|
1335
|
+
}
|
|
1336
|
+
catch (e) {
|
|
1337
|
+
// Ignore localStorage errors
|
|
1338
|
+
}
|
|
1339
|
+
// console.log('✅ Abstraxn wallet disconnected, main state reset');
|
|
1340
|
+
}
|
|
1341
|
+
catch (err) {
|
|
1342
|
+
console.error("Error disconnecting Abstraxn wallet:", err);
|
|
1343
|
+
// Continue even if Abstraxn wallet disconnect fails, but still reset state
|
|
1344
|
+
setIsConnected(false);
|
|
1345
|
+
setAddress(null);
|
|
1346
|
+
setChainId(null);
|
|
1347
|
+
setConnectionType(null);
|
|
1348
|
+
// Clear from localStorage
|
|
1349
|
+
try {
|
|
1350
|
+
localStorage.removeItem("abstraxn_connection_type");
|
|
1351
|
+
}
|
|
1352
|
+
catch (e) {
|
|
1353
|
+
// Ignore localStorage errors
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
// Final check: if neither wallet is connected, ensure main state is reset
|
|
1358
|
+
// This is a safety check in case both were disconnected
|
|
1359
|
+
const finalExternalConnected = isExternalWalletConnected;
|
|
1360
|
+
const finalAbstraxnConnected = walletRef.current?.isConnected ?? false;
|
|
1361
|
+
if (!finalExternalConnected && !finalAbstraxnConnected) {
|
|
1362
|
+
setIsConnected(false);
|
|
1363
|
+
setAddress(null);
|
|
1364
|
+
setChainId(null);
|
|
1365
|
+
setConnectionType(null);
|
|
1366
|
+
// Clear from localStorage
|
|
1367
|
+
try {
|
|
1368
|
+
localStorage.removeItem("abstraxn_connection_type");
|
|
1369
|
+
}
|
|
1370
|
+
catch (e) {
|
|
1371
|
+
// Ignore localStorage errors
|
|
1372
|
+
}
|
|
1373
|
+
// console.log('✅ Final check: All wallets disconnected, main state reset');
|
|
1374
|
+
}
|
|
1375
|
+
// Reset OTP screen and show initial login form
|
|
1376
|
+
if (onboardingRef.current) {
|
|
1377
|
+
const onboarding = onboardingRef.current;
|
|
1378
|
+
onboarding.resetToLoginForm();
|
|
1379
|
+
// If modal is visible (showing OTP screen), ensure it shows the login form
|
|
1380
|
+
if (onboarding.modalOverlay &&
|
|
1381
|
+
onboarding.modalOverlay.style.display !== "none") {
|
|
1382
|
+
// Modal is visible, just reset the form (already done above)
|
|
1383
|
+
// Ensure modal is in open state
|
|
1384
|
+
onboarding.modalOverlay.classList.remove("onboarding-modal-closing");
|
|
1385
|
+
onboarding.modalOverlay.classList.add("onboarding-modal-open");
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
catch (err) {
|
|
1390
|
+
setError(err instanceof Error ? err : new Error("Failed to disconnect"));
|
|
1391
|
+
throw err;
|
|
1392
|
+
}
|
|
1393
|
+
finally {
|
|
1394
|
+
// Clear disconnecting flag after a short delay to allow state to settle
|
|
1395
|
+
setTimeout(() => {
|
|
1396
|
+
disconnectingRef.current = false;
|
|
1397
|
+
}, 200);
|
|
1398
|
+
setLoading(false);
|
|
1399
|
+
}
|
|
1400
|
+
}, [isExternalWalletConnected, externalWalletsEnabled, wagmiDisconnect]);
|
|
1401
|
+
// Hide onboarding UI
|
|
1402
|
+
const hideOnboarding = useCallback(() => {
|
|
1403
|
+
setError(null); // Clear any previous errors
|
|
1404
|
+
if (onboardingRef.current) {
|
|
1405
|
+
const onboarding = onboardingRef.current;
|
|
1406
|
+
if (onboarding.modalOverlay) {
|
|
1407
|
+
onboarding.modalOverlay.classList.remove("onboarding-modal-open");
|
|
1408
|
+
onboarding.modalOverlay.classList.add("onboarding-modal-closing");
|
|
1409
|
+
setTimeout(() => {
|
|
1410
|
+
if (onboarding.modalOverlay) {
|
|
1411
|
+
onboarding.modalOverlay.style.display = "none";
|
|
1412
|
+
}
|
|
1413
|
+
}, 200);
|
|
1414
|
+
}
|
|
1415
|
+
document.body.style.overflow = "";
|
|
1416
|
+
}
|
|
1417
|
+
}, []);
|
|
1418
|
+
// Get address - works for both Abstraxn wallet and external wallets
|
|
1419
|
+
const getAddress = useCallback(async () => {
|
|
1420
|
+
setError(null); // Clear any previous errors
|
|
1421
|
+
// If external wallet is connected, return external wallet address
|
|
1422
|
+
if (isExternalWalletConnected && externalWalletAddress) {
|
|
1423
|
+
return externalWalletAddress;
|
|
1424
|
+
}
|
|
1425
|
+
// Otherwise use Abstraxn wallet
|
|
1426
|
+
if (!walletRef.current)
|
|
1427
|
+
throw new Error("Wallet not initialized");
|
|
1428
|
+
return await walletRef.current.getAddress();
|
|
1429
|
+
}, [isExternalWalletConnected, externalWalletAddress]);
|
|
1430
|
+
// Get chain ID - works for both Abstraxn wallet and external wallets
|
|
1431
|
+
const getChainId = useCallback(async () => {
|
|
1432
|
+
setError(null); // Clear any previous errors
|
|
1433
|
+
// If external wallet is connected, return external wallet chain ID
|
|
1434
|
+
if (isExternalWalletConnected && externalWalletChainId) {
|
|
1435
|
+
return externalWalletChainId;
|
|
1436
|
+
}
|
|
1437
|
+
// Otherwise use Abstraxn wallet
|
|
1438
|
+
if (!walletRef.current)
|
|
1439
|
+
throw new Error("Wallet not initialized");
|
|
1440
|
+
return await walletRef.current.getChainId();
|
|
1441
|
+
}, [isExternalWalletConnected, externalWalletChainId]);
|
|
1442
|
+
// Switch chain - works for both Abstraxn wallet and external wallets
|
|
1443
|
+
const switchChain = useCallback(async (newChainId) => {
|
|
1444
|
+
setError(null);
|
|
1445
|
+
// If external wallet is connected, use wagmi
|
|
1446
|
+
if (isExternalWalletConnected && wagmiSwitchChain) {
|
|
1447
|
+
try {
|
|
1448
|
+
setLoading(true);
|
|
1449
|
+
await wagmiSwitchChain.switchChainAsync({ chainId: newChainId });
|
|
1450
|
+
}
|
|
1451
|
+
catch (err) {
|
|
1452
|
+
const error = err instanceof Error ? err : new Error("Failed to switch chain");
|
|
1453
|
+
setError(error);
|
|
1454
|
+
throw error;
|
|
1455
|
+
}
|
|
1456
|
+
finally {
|
|
1457
|
+
setLoading(false);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
// Otherwise use Abstraxn wallet
|
|
1462
|
+
if (!walletRef.current)
|
|
1463
|
+
throw new Error("Wallet not initialized");
|
|
1464
|
+
try {
|
|
1465
|
+
await walletRef.current.switchChain(newChainId);
|
|
1466
|
+
}
|
|
1467
|
+
catch (err) {
|
|
1468
|
+
setError(err instanceof Error ? err : new Error("Failed to switch chain"));
|
|
1469
|
+
throw err;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}, [isExternalWalletConnected, wagmiSwitchChain]);
|
|
1473
|
+
// Sign message - works for both Abstraxn wallet and external wallets
|
|
1474
|
+
const signMessage = useCallback(async (message) => {
|
|
1475
|
+
setError(null);
|
|
1476
|
+
// If external wallet is connected, use wagmi
|
|
1477
|
+
if (isExternalWalletConnected &&
|
|
1478
|
+
externalWalletAddress &&
|
|
1479
|
+
wagmiSignMessage) {
|
|
1480
|
+
try {
|
|
1481
|
+
setLoading(true);
|
|
1482
|
+
const signature = await wagmiSignMessage.signMessageAsync({
|
|
1483
|
+
message,
|
|
1484
|
+
});
|
|
1485
|
+
return signature;
|
|
1486
|
+
}
|
|
1487
|
+
catch (err) {
|
|
1488
|
+
const error = err instanceof Error ? err : new Error("Failed to sign message");
|
|
1489
|
+
setError(error);
|
|
1490
|
+
throw error;
|
|
1491
|
+
}
|
|
1492
|
+
finally {
|
|
1493
|
+
setLoading(false);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
// Otherwise use Abstraxn wallet
|
|
1497
|
+
if (!walletRef.current)
|
|
1498
|
+
throw new Error("Wallet not initialized");
|
|
1499
|
+
try {
|
|
1500
|
+
return await walletRef.current.signMessage(message);
|
|
1501
|
+
}
|
|
1502
|
+
catch (err) {
|
|
1503
|
+
setError(err instanceof Error ? err : new Error("Failed to sign message"));
|
|
1504
|
+
throw err;
|
|
1505
|
+
}
|
|
1506
|
+
}, [isExternalWalletConnected, externalWalletAddress, wagmiSignMessage]);
|
|
1507
|
+
// Sign transaction
|
|
1508
|
+
const signTransaction = useCallback(async (tx) => {
|
|
1509
|
+
setError(null); // Clear any previous errors
|
|
1510
|
+
if (!walletRef.current)
|
|
1511
|
+
throw new Error("Wallet not initialized");
|
|
1512
|
+
try {
|
|
1513
|
+
return await walletRef.current.signTransaction(tx);
|
|
1514
|
+
}
|
|
1515
|
+
catch (err) {
|
|
1516
|
+
setError(err instanceof Error ? err : new Error("Failed to sign transaction"));
|
|
1517
|
+
throw err;
|
|
1518
|
+
}
|
|
1519
|
+
}, []);
|
|
1520
|
+
// Send transaction - works for both Abstraxn wallet and external wallets
|
|
1521
|
+
const sendTransaction = useCallback(async (tx) => {
|
|
1522
|
+
setError(null);
|
|
1523
|
+
// If external wallet is connected, use wagmi
|
|
1524
|
+
if (isExternalWalletConnected &&
|
|
1525
|
+
externalWalletAddress &&
|
|
1526
|
+
wagmiSendTransaction) {
|
|
1527
|
+
try {
|
|
1528
|
+
setLoading(true);
|
|
1529
|
+
// Convert TransactionRequest to wagmi format
|
|
1530
|
+
const wagmiTx = {
|
|
1531
|
+
to: tx.to,
|
|
1532
|
+
};
|
|
1533
|
+
if (tx.value) {
|
|
1534
|
+
wagmiTx.value =
|
|
1535
|
+
typeof tx.value === "string"
|
|
1536
|
+
? parseEther(tx.value)
|
|
1537
|
+
: BigInt(tx.value);
|
|
1538
|
+
}
|
|
1539
|
+
if (tx.data) {
|
|
1540
|
+
wagmiTx.data = tx.data;
|
|
1541
|
+
}
|
|
1542
|
+
if (tx.gasLimit) {
|
|
1543
|
+
wagmiTx.gas = tx.gasLimit;
|
|
1544
|
+
}
|
|
1545
|
+
if (tx.gasPrice) {
|
|
1546
|
+
wagmiTx.gasPrice = tx.gasPrice;
|
|
1547
|
+
}
|
|
1548
|
+
if (tx.maxFeePerGas) {
|
|
1549
|
+
wagmiTx.maxFeePerGas = tx.maxFeePerGas;
|
|
1550
|
+
}
|
|
1551
|
+
if (tx.maxPriorityFeePerGas) {
|
|
1552
|
+
wagmiTx.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;
|
|
1553
|
+
}
|
|
1554
|
+
const hash = await wagmiSendTransaction.sendTransactionAsync(wagmiTx);
|
|
1555
|
+
return {
|
|
1556
|
+
hash: hash,
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
catch (err) {
|
|
1560
|
+
const error = err instanceof Error
|
|
1561
|
+
? err
|
|
1562
|
+
: new Error("Failed to send transaction");
|
|
1563
|
+
setError(error);
|
|
1564
|
+
throw error;
|
|
1565
|
+
}
|
|
1566
|
+
finally {
|
|
1567
|
+
setLoading(false);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
// Otherwise use Abstraxn wallet
|
|
1571
|
+
if (!walletRef.current)
|
|
1572
|
+
throw new Error("Wallet not initialized");
|
|
1573
|
+
try {
|
|
1574
|
+
return await walletRef.current.sendTransaction(tx);
|
|
1575
|
+
}
|
|
1576
|
+
catch (err) {
|
|
1577
|
+
setError(err instanceof Error ? err : new Error("Failed to send transaction"));
|
|
1578
|
+
throw err;
|
|
1579
|
+
}
|
|
1580
|
+
}, [isExternalWalletConnected, externalWalletAddress, wagmiSendTransaction]);
|
|
1581
|
+
// Sign transaction via API (returns signed transaction)
|
|
1582
|
+
const signTransactionViaAPI = useCallback(async (unsignedTransaction, fromAddress) => {
|
|
1583
|
+
if (!walletRef.current)
|
|
1584
|
+
throw new Error("Wallet not initialized");
|
|
1585
|
+
setLoading(true);
|
|
1586
|
+
setError(null);
|
|
1587
|
+
try {
|
|
1588
|
+
return await walletRef.current.signTransactionViaAPI(unsignedTransaction, fromAddress);
|
|
1589
|
+
}
|
|
1590
|
+
catch (err) {
|
|
1591
|
+
const error = err instanceof Error ? err : new Error("Failed to sign transaction");
|
|
1592
|
+
setError(error);
|
|
1593
|
+
throw error;
|
|
1594
|
+
}
|
|
1595
|
+
finally {
|
|
1596
|
+
setLoading(false);
|
|
1597
|
+
}
|
|
1598
|
+
}, []);
|
|
1599
|
+
// Sign and send transaction using backend API
|
|
1600
|
+
const signAndSendTransaction = useCallback(async (unsignedTransaction, fromAddress, rpcUrl) => {
|
|
1601
|
+
if (!walletRef.current)
|
|
1602
|
+
throw new Error("Wallet not initialized");
|
|
1603
|
+
setLoading(true);
|
|
1604
|
+
setError(null);
|
|
1605
|
+
try {
|
|
1606
|
+
return await walletRef.current.signAndSendTransaction(unsignedTransaction, fromAddress, rpcUrl);
|
|
1607
|
+
}
|
|
1608
|
+
catch (err) {
|
|
1609
|
+
const error = err instanceof Error
|
|
1610
|
+
? err
|
|
1611
|
+
: new Error("Failed to sign and send transaction");
|
|
1612
|
+
setError(error);
|
|
1613
|
+
throw error;
|
|
1614
|
+
}
|
|
1615
|
+
finally {
|
|
1616
|
+
setLoading(false);
|
|
1617
|
+
}
|
|
1618
|
+
}, []);
|
|
1619
|
+
// Login with email OTP (initiate OTP)
|
|
1620
|
+
const loginWithOTP = useCallback(async (email) => {
|
|
1621
|
+
if (!walletRef.current)
|
|
1622
|
+
throw new Error("Wallet not initialized");
|
|
1623
|
+
setLoading(true);
|
|
1624
|
+
setError(null);
|
|
1625
|
+
try {
|
|
1626
|
+
const connectionTypeValue = "email";
|
|
1627
|
+
setConnectionType(connectionTypeValue);
|
|
1628
|
+
try {
|
|
1629
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
1630
|
+
}
|
|
1631
|
+
catch (e) {
|
|
1632
|
+
// Ignore localStorage errors
|
|
1633
|
+
}
|
|
1634
|
+
const result = await walletRef.current.loginWithOTP(email);
|
|
1635
|
+
return result;
|
|
1636
|
+
}
|
|
1637
|
+
catch (err) {
|
|
1638
|
+
const error = err instanceof Error ? err : new Error("Failed to initiate OTP");
|
|
1639
|
+
setError(error);
|
|
1640
|
+
throw error;
|
|
1641
|
+
}
|
|
1642
|
+
finally {
|
|
1643
|
+
setLoading(false);
|
|
1644
|
+
}
|
|
1645
|
+
}, []);
|
|
1646
|
+
// Verify OTP
|
|
1647
|
+
const verifyOTP = useCallback(async (otpId, otpCode) => {
|
|
1648
|
+
if (!walletRef.current)
|
|
1649
|
+
throw new Error("Wallet not initialized");
|
|
1650
|
+
setLoading(true);
|
|
1651
|
+
setError(null);
|
|
1652
|
+
try {
|
|
1653
|
+
// Set connection type for email OTP before verification
|
|
1654
|
+
const connectionTypeValue = "email";
|
|
1655
|
+
setConnectionType(connectionTypeValue);
|
|
1656
|
+
try {
|
|
1657
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
1658
|
+
}
|
|
1659
|
+
catch (e) {
|
|
1660
|
+
// Ignore localStorage errors
|
|
1661
|
+
}
|
|
1662
|
+
const user = await walletRef.current.verifyOTP(otpId, otpCode);
|
|
1663
|
+
setIsConnected(true);
|
|
1664
|
+
setUser(user);
|
|
1665
|
+
setConnectionType("email");
|
|
1666
|
+
try {
|
|
1667
|
+
localStorage.setItem("abstraxn_connection_type", "email");
|
|
1668
|
+
}
|
|
1669
|
+
catch (e) {
|
|
1670
|
+
// Ignore localStorage errors
|
|
1671
|
+
}
|
|
1672
|
+
// Load whoami and address after verification
|
|
1673
|
+
try {
|
|
1674
|
+
const whoamiInfo = await walletRef.current.getWhoami();
|
|
1675
|
+
setWhoami(whoamiInfo);
|
|
1676
|
+
const addr = await walletRef.current.getAddress();
|
|
1677
|
+
setAddress(addr);
|
|
1678
|
+
const cid = await walletRef.current.getChainId();
|
|
1679
|
+
setChainId(cid);
|
|
1680
|
+
}
|
|
1681
|
+
catch (err) {
|
|
1682
|
+
console.error("Error loading whoami after OTP verification:", err);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
catch (err) {
|
|
1686
|
+
const error = err instanceof Error ? err : new Error("Failed to verify OTP");
|
|
1687
|
+
setError(error);
|
|
1688
|
+
throw error;
|
|
1689
|
+
}
|
|
1690
|
+
finally {
|
|
1691
|
+
setLoading(false);
|
|
1692
|
+
}
|
|
1693
|
+
}, []);
|
|
1694
|
+
// Login with Google
|
|
1695
|
+
const loginWithGoogle = useCallback(async () => {
|
|
1696
|
+
if (!walletRef.current)
|
|
1697
|
+
throw new Error("Wallet not initialized");
|
|
1698
|
+
setLoading(true);
|
|
1699
|
+
setError(null);
|
|
1700
|
+
try {
|
|
1701
|
+
const connectionTypeValue = "google";
|
|
1702
|
+
setConnectionType(connectionTypeValue);
|
|
1703
|
+
try {
|
|
1704
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
1705
|
+
}
|
|
1706
|
+
catch (e) {
|
|
1707
|
+
// Ignore localStorage errors
|
|
1708
|
+
}
|
|
1709
|
+
await walletRef.current.loginWithGoogle();
|
|
1710
|
+
// Note: This will redirect, so loading state will be reset on callback
|
|
1711
|
+
}
|
|
1712
|
+
catch (err) {
|
|
1713
|
+
const error = err instanceof Error
|
|
1714
|
+
? err
|
|
1715
|
+
: new Error("Failed to initiate Google login");
|
|
1716
|
+
setError(error);
|
|
1717
|
+
setLoading(false);
|
|
1718
|
+
throw error;
|
|
1719
|
+
}
|
|
1720
|
+
}, []);
|
|
1721
|
+
// Handle Google callback
|
|
1722
|
+
const handleGoogleCallback = useCallback(async (_force = false) => {
|
|
1723
|
+
if (!walletRef.current)
|
|
1724
|
+
return;
|
|
1725
|
+
setLoading(true);
|
|
1726
|
+
setError(null);
|
|
1727
|
+
try {
|
|
1728
|
+
// handleGoogleCallback() will initialize storage internally
|
|
1729
|
+
// Don't call connect() here as it checks for authentication which isn't available yet
|
|
1730
|
+
const user = await walletRef.current.handleGoogleCallback();
|
|
1731
|
+
if (user) {
|
|
1732
|
+
setUser(user);
|
|
1733
|
+
// Restore connection type for Google OAuth
|
|
1734
|
+
try {
|
|
1735
|
+
const storedType = localStorage.getItem("abstraxn_connection_type");
|
|
1736
|
+
if (storedType === "google") {
|
|
1737
|
+
setConnectionType("google");
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
catch (e) {
|
|
1741
|
+
// Ignore localStorage errors
|
|
1742
|
+
}
|
|
1743
|
+
// handleGoogleCallback() already calls whoami and emits 'connect' event
|
|
1744
|
+
// The 'connect' event handler will update whoami, address, and chainId
|
|
1745
|
+
// So we don't need to do it here to avoid duplicate whoami calls
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
catch (err) {
|
|
1749
|
+
const error = err instanceof Error
|
|
1750
|
+
? err
|
|
1751
|
+
: new Error("Failed to handle Google callback");
|
|
1752
|
+
setError(error);
|
|
1753
|
+
throw error;
|
|
1754
|
+
}
|
|
1755
|
+
finally {
|
|
1756
|
+
setLoading(false);
|
|
1757
|
+
}
|
|
1758
|
+
}, []);
|
|
1759
|
+
// Refresh whoami - makes a fresh API call and updates state
|
|
1760
|
+
// For external wallets, whoami is not applicable (no API backend)
|
|
1761
|
+
const refreshWhoami = useCallback(async () => {
|
|
1762
|
+
// If external wallet is connected, return null (whoami is only for Abstraxn wallet)
|
|
1763
|
+
if (isExternalWalletConnected) {
|
|
1764
|
+
return null;
|
|
1765
|
+
}
|
|
1766
|
+
if (!walletRef.current) {
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1769
|
+
// Check if Abstraxn wallet is actually connected before calling refreshWhoami
|
|
1770
|
+
if (!walletRef.current.isConnected) {
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
setLoading(true);
|
|
1774
|
+
setError(null);
|
|
1775
|
+
try {
|
|
1776
|
+
const whoamiInfo = await walletRef.current.refreshWhoami();
|
|
1777
|
+
if (whoamiInfo) {
|
|
1778
|
+
setWhoami(whoamiInfo);
|
|
1779
|
+
// Update address if available in whoami response
|
|
1780
|
+
if (whoamiInfo.address) {
|
|
1781
|
+
setAddress(whoamiInfo.address);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return whoamiInfo;
|
|
1785
|
+
}
|
|
1786
|
+
catch (err) {
|
|
1787
|
+
const error = err instanceof Error ? err : new Error("Failed to refresh whoami");
|
|
1788
|
+
setError(error);
|
|
1789
|
+
throw error;
|
|
1790
|
+
}
|
|
1791
|
+
finally {
|
|
1792
|
+
setLoading(false);
|
|
1793
|
+
}
|
|
1794
|
+
}, [isExternalWalletConnected]);
|
|
1795
|
+
// Sync external wallet state from wagmi
|
|
1796
|
+
useEffect(() => {
|
|
1797
|
+
let checkAddressTimeout = null;
|
|
1798
|
+
// Prevent infinite loops - if we're already updating, skip
|
|
1799
|
+
if (isUpdatingRef.current) {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (!externalWalletsEnabled || !wagmiAccount) {
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
// Prevent auto-connect (but allow restoration from persistence)
|
|
1806
|
+
// Only auto-disconnect if:
|
|
1807
|
+
// 1. wagmiAccount is connected
|
|
1808
|
+
// 2. We don't have explicitConnectionRef set (meaning user didn't explicitly connect)
|
|
1809
|
+
// 3. We haven't already handled auto-disconnect
|
|
1810
|
+
// 4. We don't have a stored address (meaning this isn't a restoration from persistence)
|
|
1811
|
+
if (wagmiAccount.isConnected &&
|
|
1812
|
+
!explicitConnectionRef.current &&
|
|
1813
|
+
!autoDisconnectHandledRef.current &&
|
|
1814
|
+
lastAddressRef.current === null) {
|
|
1815
|
+
autoDisconnectHandledRef.current = true;
|
|
1816
|
+
if (wagmiDisconnect) {
|
|
1817
|
+
setTimeout(() => {
|
|
1818
|
+
wagmiDisconnect.disconnect();
|
|
1819
|
+
}, 0);
|
|
1820
|
+
}
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
// If wagmiAccount is not connected and we have explicitConnectionRef set to false,
|
|
1824
|
+
// it means we explicitly disconnected, so don't try to reconnect
|
|
1825
|
+
if (!wagmiAccount.isConnected) {
|
|
1826
|
+
autoDisconnectHandledRef.current = false;
|
|
1827
|
+
// If we're in the middle of disconnecting, don't interfere
|
|
1828
|
+
if (disconnectingRef.current) {
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
// If we explicitly disconnected (explicitConnectionRef is false and we were connected),
|
|
1832
|
+
// ensure state is reset immediately
|
|
1833
|
+
if (!explicitConnectionRef.current && isExternalWalletConnected) {
|
|
1834
|
+
isUpdatingRef.current = true;
|
|
1835
|
+
setIsExternalWalletConnected(false);
|
|
1836
|
+
setExternalWalletAddress(null);
|
|
1837
|
+
setExternalWalletChainId(null);
|
|
1838
|
+
lastAddressRef.current = null;
|
|
1839
|
+
lastChainIdRef.current = null;
|
|
1840
|
+
// Only reset main state if Abstraxn wallet is also not connected
|
|
1841
|
+
if (!walletRef.current?.isConnected) {
|
|
1842
|
+
setIsConnected(false);
|
|
1843
|
+
setAddress(null);
|
|
1844
|
+
setChainId(null);
|
|
1845
|
+
}
|
|
1846
|
+
lastConnectionTimeRef.current = 0;
|
|
1847
|
+
isUpdatingRef.current = false;
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
// If we're not connected but explicitConnectionRef is true, it means we're trying to reconnect
|
|
1851
|
+
// Don't reset state in this case - wait for the connection to complete
|
|
1852
|
+
if (!wagmiAccount.isConnected && explicitConnectionRef.current) {
|
|
1853
|
+
// Connection is in progress, don't interfere
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
// Check if external wallet is connected
|
|
1858
|
+
if (wagmiAccount.isConnected && wagmiAccount.address) {
|
|
1859
|
+
const walletAddress = wagmiAccount.address;
|
|
1860
|
+
// Ensure address is properly formatted (should be 0x...)
|
|
1861
|
+
const formattedAddress = walletAddress && typeof walletAddress === "string"
|
|
1862
|
+
? walletAddress.toLowerCase()
|
|
1863
|
+
: walletAddress;
|
|
1864
|
+
// Use wagmiChainIdHook first (more reliable), then fallback to wagmiAccount.chainId
|
|
1865
|
+
const currentChainId = wagmiChainIdHook || wagmiAccount.chainId || null;
|
|
1866
|
+
// Check if address or chainId actually changed to prevent unnecessary updates
|
|
1867
|
+
const addressChanged = lastAddressRef.current !== formattedAddress;
|
|
1868
|
+
const chainIdChanged = lastChainIdRef.current !== currentChainId;
|
|
1869
|
+
// Also check if we need to reconnect (lastAddressRef is null but wagmiAccount is connected)
|
|
1870
|
+
// This handles the case where user disconnects and then reconnects, OR page reload
|
|
1871
|
+
const needsReconnect = lastAddressRef.current === null &&
|
|
1872
|
+
wagmiAccount.isConnected &&
|
|
1873
|
+
formattedAddress;
|
|
1874
|
+
// Only update if something actually changed OR if we need to reconnect
|
|
1875
|
+
if (addressChanged || chainIdChanged || needsReconnect) {
|
|
1876
|
+
isUpdatingRef.current = true;
|
|
1877
|
+
// If this is a restoration (needsReconnect), set explicitConnectionRef to prevent auto-disconnect
|
|
1878
|
+
if (needsReconnect) {
|
|
1879
|
+
explicitConnectionRef.current = true;
|
|
1880
|
+
autoDisconnectHandledRef.current = false;
|
|
1881
|
+
}
|
|
1882
|
+
// Update address if it changed or if we need to reconnect
|
|
1883
|
+
if (addressChanged || needsReconnect) {
|
|
1884
|
+
setIsExternalWalletConnected(true);
|
|
1885
|
+
setExternalWalletAddress(formattedAddress);
|
|
1886
|
+
setIsConnected(true);
|
|
1887
|
+
setAddress(formattedAddress);
|
|
1888
|
+
lastAddressRef.current = formattedAddress;
|
|
1889
|
+
lastConnectionTimeRef.current = Date.now();
|
|
1890
|
+
}
|
|
1891
|
+
// Update chainId if it changed or if we need to reconnect
|
|
1892
|
+
if ((chainIdChanged && currentChainId) ||
|
|
1893
|
+
(needsReconnect && currentChainId)) {
|
|
1894
|
+
setExternalWalletChainId(currentChainId);
|
|
1895
|
+
setChainId(currentChainId);
|
|
1896
|
+
lastChainIdRef.current = currentChainId;
|
|
1897
|
+
}
|
|
1898
|
+
isUpdatingRef.current = false;
|
|
1899
|
+
// Close the onboarding modal after successful external wallet connection (only once)
|
|
1900
|
+
if (addressChanged && onboardingRef.current) {
|
|
1901
|
+
const onboardingAny = onboardingRef.current;
|
|
1902
|
+
if (onboardingAny.modalOverlay) {
|
|
1903
|
+
setTimeout(() => {
|
|
1904
|
+
if (onboardingAny.modalOverlay) {
|
|
1905
|
+
onboardingAny.modalOverlay.classList.remove("onboarding-modal-open");
|
|
1906
|
+
onboardingAny.modalOverlay.classList.add("onboarding-modal-closing");
|
|
1907
|
+
setTimeout(() => {
|
|
1908
|
+
if (onboardingAny.modalOverlay) {
|
|
1909
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
1910
|
+
}
|
|
1911
|
+
}, 200);
|
|
1912
|
+
document.body.style.overflow = "";
|
|
1913
|
+
}
|
|
1914
|
+
}, 100);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
else if (!wagmiAccount.isConnected) {
|
|
1920
|
+
// Only reset state if:
|
|
1921
|
+
// 1. We have a stored address (meaning we were connected), AND
|
|
1922
|
+
// 2. It's been more than 1 second since last connection (to avoid premature reset during re-renders), AND
|
|
1923
|
+
// 3. This is not an auto-disconnect scenario
|
|
1924
|
+
const timeSinceLastConnection = Date.now() - lastConnectionTimeRef.current;
|
|
1925
|
+
const shouldReset = lastAddressRef.current !== null &&
|
|
1926
|
+
timeSinceLastConnection > 1000 &&
|
|
1927
|
+
!autoDisconnectHandledRef.current;
|
|
1928
|
+
if (shouldReset) {
|
|
1929
|
+
isUpdatingRef.current = true;
|
|
1930
|
+
setIsExternalWalletConnected(false);
|
|
1931
|
+
setExternalWalletAddress(null);
|
|
1932
|
+
setExternalWalletChainId(null);
|
|
1933
|
+
lastAddressRef.current = null;
|
|
1934
|
+
lastChainIdRef.current = null;
|
|
1935
|
+
// Only reset if Abstraxn wallet is also not connected
|
|
1936
|
+
if (!walletRef.current?.isConnected) {
|
|
1937
|
+
setIsConnected(false);
|
|
1938
|
+
setAddress(null);
|
|
1939
|
+
setChainId(null);
|
|
1940
|
+
}
|
|
1941
|
+
explicitConnectionRef.current = false;
|
|
1942
|
+
lastConnectionTimeRef.current = 0;
|
|
1943
|
+
isUpdatingRef.current = false;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
// Cleanup function
|
|
1947
|
+
return () => {
|
|
1948
|
+
if (checkAddressTimeout) {
|
|
1949
|
+
clearTimeout(checkAddressTimeout);
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
}, [
|
|
1953
|
+
wagmiAccount?.isConnected,
|
|
1954
|
+
wagmiAccount?.address,
|
|
1955
|
+
wagmiAccount?.chainId,
|
|
1956
|
+
wagmiChainIdHook,
|
|
1957
|
+
externalWalletsEnabled,
|
|
1958
|
+
wagmiDisconnect,
|
|
1959
|
+
]);
|
|
1960
|
+
// Connect external wallet
|
|
1961
|
+
const connectExternalWallet = useCallback(async (connectorId) => {
|
|
1962
|
+
if (!externalWalletsEnabled || !wagmiConnect || !wagmiDisconnect) {
|
|
1963
|
+
const error = new Error("External wallets are not enabled or wagmi is not initialized");
|
|
1964
|
+
throw error;
|
|
1965
|
+
}
|
|
1966
|
+
setLoading(true);
|
|
1967
|
+
setError(null);
|
|
1968
|
+
try {
|
|
1969
|
+
if (wagmiAccount?.isConnected) {
|
|
1970
|
+
try {
|
|
1971
|
+
await wagmiDisconnect.disconnect();
|
|
1972
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1973
|
+
}
|
|
1974
|
+
catch (disconnectErr) {
|
|
1975
|
+
// Ignore
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
// Try to find connector by exact ID first
|
|
1979
|
+
let connector = wagmiConnect.connectors.find((c) => c.id === connectorId);
|
|
1980
|
+
// If not found, try to find by matching patterns for specific wallets
|
|
1981
|
+
if (!connector) {
|
|
1982
|
+
const connectorIdLower = connectorId.toLowerCase();
|
|
1983
|
+
// Check for MetaMask - prioritize io.metamask over injected
|
|
1984
|
+
if (connectorIdLower.includes("metamask") ||
|
|
1985
|
+
connectorIdLower.includes("io.metamask")) {
|
|
1986
|
+
// First, try to find io.metamask specifically
|
|
1987
|
+
connector = wagmiConnect.connectors.find((c) => {
|
|
1988
|
+
const id = c.id.toLowerCase();
|
|
1989
|
+
return id === "io.metamask" || id.includes("io.metamask");
|
|
1990
|
+
});
|
|
1991
|
+
// If io.metamask not found, fall back to injected (but only if MetaMask is requested)
|
|
1992
|
+
if (!connector) {
|
|
1993
|
+
connector = wagmiConnect.connectors.find((c) => {
|
|
1994
|
+
const id = c.id.toLowerCase();
|
|
1995
|
+
const name = c.name?.toLowerCase() || "";
|
|
1996
|
+
return ((id.includes("injected") || id.includes("metamask")) &&
|
|
1997
|
+
(name.includes("metamask") ||
|
|
1998
|
+
(typeof window !== "undefined" &&
|
|
1999
|
+
window.ethereum?.isMetaMask)));
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
// Check for generic injected (only if not MetaMask)
|
|
2004
|
+
else if (connectorIdLower.includes("injected")) {
|
|
2005
|
+
connector = wagmiConnect.connectors.find((c) => {
|
|
2006
|
+
const id = c.id.toLowerCase();
|
|
2007
|
+
// Exclude io.metamask and other specific connectors
|
|
2008
|
+
return (id === "injected" &&
|
|
2009
|
+
!id.includes("io.metamask") &&
|
|
2010
|
+
!id.includes("com.coinbase") &&
|
|
2011
|
+
!id.includes("app.phantom"));
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
// Check for Coinbase Wallet
|
|
2015
|
+
else if (connectorIdLower.includes("coinbase") ||
|
|
2016
|
+
connectorIdLower.includes("com.coinbase.wallet")) {
|
|
2017
|
+
connector = wagmiConnect.connectors.find((c) => {
|
|
2018
|
+
const id = c.id.toLowerCase();
|
|
2019
|
+
const name = c.name?.toLowerCase() || "";
|
|
2020
|
+
return (id.includes("coinbase") ||
|
|
2021
|
+
id.includes("com.coinbase") ||
|
|
2022
|
+
name.includes("coinbase"));
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
// Check for Phantom
|
|
2026
|
+
else if (connectorIdLower.includes("phantom") ||
|
|
2027
|
+
connectorIdLower.includes("app.phantom")) {
|
|
2028
|
+
connector = wagmiConnect.connectors.find((c) => {
|
|
2029
|
+
const id = c.id.toLowerCase();
|
|
2030
|
+
const name = c.name?.toLowerCase() || "";
|
|
2031
|
+
return (id.includes("phantom") ||
|
|
2032
|
+
id.includes("app.phantom") ||
|
|
2033
|
+
name.includes("phantom"));
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
// Check for WalletConnect
|
|
2037
|
+
else if (connectorIdLower.includes("walletconnect") ||
|
|
2038
|
+
connectorIdLower.includes("wallet_connect")) {
|
|
2039
|
+
connector = wagmiConnect.connectors.find((c) => {
|
|
2040
|
+
const id = c.id.toLowerCase();
|
|
2041
|
+
const name = c.name?.toLowerCase() || "";
|
|
2042
|
+
return (id.includes("walletconnect") ||
|
|
2043
|
+
id.includes("wallet_connect") ||
|
|
2044
|
+
name.includes("walletconnect") ||
|
|
2045
|
+
name.includes("wallet connect"));
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
// Try to find any connector that matches (fallback)
|
|
2049
|
+
else {
|
|
2050
|
+
connector = wagmiConnect.connectors.find((c) => {
|
|
2051
|
+
const id = c.id.toLowerCase();
|
|
2052
|
+
return id === connectorIdLower || id.includes(connectorIdLower);
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
if (!connector) {
|
|
2057
|
+
const availableIds = wagmiConnect.connectors
|
|
2058
|
+
.map((c) => c.id)
|
|
2059
|
+
.join(", ");
|
|
2060
|
+
const error = new Error(`Connector "${connectorId}" not found. Available connectors: ${availableIds}`);
|
|
2061
|
+
throw error;
|
|
2062
|
+
}
|
|
2063
|
+
autoDisconnectHandledRef.current = false;
|
|
2064
|
+
explicitConnectionRef.current = true;
|
|
2065
|
+
// CRITICAL: Reset refs before connecting to ensure reconnect is detected
|
|
2066
|
+
// This ensures that when we reconnect after a disconnect, the address change is detected
|
|
2067
|
+
lastAddressRef.current = null;
|
|
2068
|
+
lastChainIdRef.current = null;
|
|
2069
|
+
try {
|
|
2070
|
+
// Use connectAsync to properly await the connection
|
|
2071
|
+
const result = await wagmiConnect.connectAsync({ connector });
|
|
2072
|
+
// Use the result directly to set state immediately
|
|
2073
|
+
// This avoids waiting for the useAccount hook to update, which can be delayed
|
|
2074
|
+
if (result.accounts && result.accounts.length > 0) {
|
|
2075
|
+
const address = result.accounts[0];
|
|
2076
|
+
const chainId = result.chainId;
|
|
2077
|
+
const formattedAddress = address.toLowerCase();
|
|
2078
|
+
// Set state directly
|
|
2079
|
+
setExternalWalletAddress(formattedAddress);
|
|
2080
|
+
setIsExternalWalletConnected(true);
|
|
2081
|
+
setAddress(formattedAddress);
|
|
2082
|
+
setIsConnected(true);
|
|
2083
|
+
if (chainId) {
|
|
2084
|
+
setChainId(chainId);
|
|
2085
|
+
setExternalWalletChainId(chainId);
|
|
2086
|
+
}
|
|
2087
|
+
// Track connection type
|
|
2088
|
+
const connectorId = connector.id.toLowerCase();
|
|
2089
|
+
const connectorName = connector.name?.toLowerCase() || "";
|
|
2090
|
+
let connectionTypeValue;
|
|
2091
|
+
// Map connector to connection type
|
|
2092
|
+
if (connectorId.includes("metamask") ||
|
|
2093
|
+
connectorId.includes("io.metamask") ||
|
|
2094
|
+
connectorName.includes("metamask")) {
|
|
2095
|
+
connectionTypeValue = "metamask";
|
|
2096
|
+
}
|
|
2097
|
+
else if (connectorId.includes("walletconnect") ||
|
|
2098
|
+
connectorId.includes("wallet_connect") ||
|
|
2099
|
+
connectorName.includes("walletconnect")) {
|
|
2100
|
+
connectionTypeValue = "walletconnect";
|
|
2101
|
+
}
|
|
2102
|
+
else if (connectorId.includes("coinbase") ||
|
|
2103
|
+
connectorId.includes("com.coinbase") ||
|
|
2104
|
+
connectorName.includes("coinbase")) {
|
|
2105
|
+
connectionTypeValue = "coinbase";
|
|
2106
|
+
}
|
|
2107
|
+
else if (connectorId.includes("phantom") ||
|
|
2108
|
+
connectorId.includes("app.phantom") ||
|
|
2109
|
+
connectorName.includes("phantom")) {
|
|
2110
|
+
connectionTypeValue = "phantom";
|
|
2111
|
+
}
|
|
2112
|
+
else if (connectorId.includes("injected")) {
|
|
2113
|
+
connectionTypeValue = "injected";
|
|
2114
|
+
}
|
|
2115
|
+
else {
|
|
2116
|
+
connectionTypeValue = connectorName || connectorId;
|
|
2117
|
+
}
|
|
2118
|
+
setConnectionType(connectionTypeValue);
|
|
2119
|
+
try {
|
|
2120
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
2121
|
+
}
|
|
2122
|
+
catch (e) { }
|
|
2123
|
+
// Update refs
|
|
2124
|
+
lastConnectionTimeRef.current = Date.now();
|
|
2125
|
+
lastAddressRef.current = formattedAddress;
|
|
2126
|
+
lastChainIdRef.current = chainId;
|
|
2127
|
+
explicitConnectionRef.current = true;
|
|
2128
|
+
// Close the onboarding modal immediately
|
|
2129
|
+
if (onboardingRef.current) {
|
|
2130
|
+
const onboardingAny = onboardingRef.current;
|
|
2131
|
+
if (onboardingAny.modalOverlay) {
|
|
2132
|
+
setTimeout(() => {
|
|
2133
|
+
if (onboardingAny.modalOverlay) {
|
|
2134
|
+
onboardingAny.modalOverlay.classList.remove("onboarding-modal-open");
|
|
2135
|
+
onboardingAny.modalOverlay.classList.add("onboarding-modal-closing");
|
|
2136
|
+
setTimeout(() => {
|
|
2137
|
+
if (onboardingAny.modalOverlay) {
|
|
2138
|
+
onboardingAny.modalOverlay.style.display = "none";
|
|
2139
|
+
}
|
|
2140
|
+
}, 200);
|
|
2141
|
+
document.body.style.overflow = "";
|
|
2142
|
+
}
|
|
2143
|
+
}, 100);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
catch (connectError) {
|
|
2149
|
+
console.error('WalletConnect connection error:', connectError);
|
|
2150
|
+
explicitConnectionRef.current = false;
|
|
2151
|
+
throw connectError;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
catch (err) {
|
|
2155
|
+
explicitConnectionRef.current = false;
|
|
2156
|
+
const error = err instanceof Error
|
|
2157
|
+
? err
|
|
2158
|
+
: new Error(`Failed to connect external wallet: ${err instanceof Error ? err.message : String(err)}`);
|
|
2159
|
+
setError(error);
|
|
2160
|
+
throw error;
|
|
2161
|
+
}
|
|
2162
|
+
finally {
|
|
2163
|
+
setLoading(false);
|
|
2164
|
+
}
|
|
2165
|
+
}, [
|
|
2166
|
+
externalWalletsEnabled,
|
|
2167
|
+
wagmiConnect,
|
|
2168
|
+
wagmiDisconnect,
|
|
2169
|
+
wagmiAccount?.isConnected,
|
|
2170
|
+
]);
|
|
2171
|
+
// Disconnect external wallet
|
|
2172
|
+
const disconnectExternalWallet = useCallback(async () => {
|
|
2173
|
+
if (!externalWalletsEnabled || !wagmiDisconnect) {
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
setLoading(true);
|
|
2177
|
+
setError(null);
|
|
2178
|
+
disconnectingRef.current = true; // Set flag to prevent useEffect from interfering
|
|
2179
|
+
try {
|
|
2180
|
+
const wasAbstraxnConnected = walletRef.current?.isConnected ?? false;
|
|
2181
|
+
// Await the disconnect to ensure it completes
|
|
2182
|
+
await wagmiDisconnect.disconnect();
|
|
2183
|
+
// Wait a bit to ensure wagmi state updates
|
|
2184
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
2185
|
+
// Reset external wallet state immediately
|
|
2186
|
+
setIsExternalWalletConnected(false);
|
|
2187
|
+
setExternalWalletAddress(null);
|
|
2188
|
+
setExternalWalletChainId(null);
|
|
2189
|
+
setConnectionType(null);
|
|
2190
|
+
// Clear from localStorage
|
|
2191
|
+
try {
|
|
2192
|
+
localStorage.removeItem("abstraxn_connection_type");
|
|
2193
|
+
}
|
|
2194
|
+
catch (e) {
|
|
2195
|
+
// Ignore localStorage errors
|
|
2196
|
+
}
|
|
2197
|
+
explicitConnectionRef.current = false;
|
|
2198
|
+
lastConnectionTimeRef.current = 0;
|
|
2199
|
+
// CRITICAL: Reset refs so reconnect is detected as a new connection
|
|
2200
|
+
lastAddressRef.current = null;
|
|
2201
|
+
lastChainIdRef.current = null;
|
|
2202
|
+
// Always reset main state when disconnecting external wallet
|
|
2203
|
+
// The context value is computed as isExternalWalletConnected || isConnected,
|
|
2204
|
+
// so setting both to false ensures the overall isConnected becomes false
|
|
2205
|
+
setIsConnected(false);
|
|
2206
|
+
setAddress(null);
|
|
2207
|
+
setChainId(null);
|
|
2208
|
+
}
|
|
2209
|
+
catch (err) {
|
|
2210
|
+
const error = err instanceof Error
|
|
2211
|
+
? err
|
|
2212
|
+
: new Error("Failed to disconnect external wallet");
|
|
2213
|
+
console.error("Error disconnecting external wallet:", error);
|
|
2214
|
+
setError(error);
|
|
2215
|
+
throw error;
|
|
2216
|
+
}
|
|
2217
|
+
finally {
|
|
2218
|
+
// Clear disconnecting flag after a short delay to allow state to settle
|
|
2219
|
+
setTimeout(() => {
|
|
2220
|
+
disconnectingRef.current = false;
|
|
2221
|
+
}, 200);
|
|
2222
|
+
setLoading(false);
|
|
2223
|
+
}
|
|
2224
|
+
}, [externalWalletsEnabled, wagmiDisconnect]);
|
|
2225
|
+
// Helper function to get network name from chain ID
|
|
2226
|
+
const getNetworkName = useCallback((chainId) => {
|
|
2227
|
+
const chainNames = {
|
|
2228
|
+
1: "Ethereum Mainnet",
|
|
2229
|
+
5: "Goerli",
|
|
2230
|
+
11155111: "Sepolia",
|
|
2231
|
+
137: "Polygon",
|
|
2232
|
+
80001: "Mumbai",
|
|
2233
|
+
80002: "Polygon Amoy",
|
|
2234
|
+
8453: "Base",
|
|
2235
|
+
84531: "Base Goerli",
|
|
2236
|
+
42161: "Arbitrum One",
|
|
2237
|
+
421613: "Arbitrum Goerli",
|
|
2238
|
+
10: "Optimism",
|
|
2239
|
+
420: "Optimism Goerli",
|
|
2240
|
+
56: "BNB Smart Chain",
|
|
2241
|
+
97: "BNB Smart Chain Testnet",
|
|
2242
|
+
};
|
|
2243
|
+
return chainNames[chainId] || `Chain ${chainId}`;
|
|
2244
|
+
}, []);
|
|
2245
|
+
// Switch external wallet chain (dedicated function for external wallets)
|
|
2246
|
+
const switchExternalWalletChain = useCallback(async (chainId) => {
|
|
2247
|
+
if (!externalWalletsEnabled || !wagmiSwitchChain) {
|
|
2248
|
+
const error = new Error("External wallets are not enabled or chain switching is not available");
|
|
2249
|
+
setError(error);
|
|
2250
|
+
throw error;
|
|
2251
|
+
}
|
|
2252
|
+
setError(null);
|
|
2253
|
+
setLoading(true);
|
|
2254
|
+
try {
|
|
2255
|
+
// Use switchChainAsync for wagmi v3
|
|
2256
|
+
await wagmiSwitchChain.switchChainAsync({ chainId });
|
|
2257
|
+
// Update local state - wagmi will update chainId via useChainId hook
|
|
2258
|
+
// But we also update our local state for immediate feedback
|
|
2259
|
+
setExternalWalletChainId(chainId);
|
|
2260
|
+
}
|
|
2261
|
+
catch (err) {
|
|
2262
|
+
const error = err instanceof Error
|
|
2263
|
+
? err
|
|
2264
|
+
: new Error(`Failed to switch chain: ${err instanceof Error ? err.message : String(err)}`);
|
|
2265
|
+
setError(error);
|
|
2266
|
+
throw error;
|
|
2267
|
+
}
|
|
2268
|
+
finally {
|
|
2269
|
+
setLoading(false);
|
|
2270
|
+
}
|
|
2271
|
+
}, [externalWalletsEnabled, wagmiSwitchChain]);
|
|
2272
|
+
// Get available connectors - return all connectors with their IDs and names
|
|
2273
|
+
// Filter out generic 'injected' connector if 'io.metamask' is available (prefer specific connectors)
|
|
2274
|
+
// The ExternalWalletButtons component will handle additional deduplication and display
|
|
2275
|
+
const availableConnectors = externalWalletsEnabled && wagmiConnect && wagmiConnect.connectors
|
|
2276
|
+
? wagmiConnect.connectors
|
|
2277
|
+
.filter((connector) => {
|
|
2278
|
+
// If io.metamask exists, exclude generic 'injected' connector
|
|
2279
|
+
const hasIoMetaMask = wagmiConnect.connectors.some((c) => c.id === "io.metamask");
|
|
2280
|
+
if (hasIoMetaMask && connector.id === "injected") {
|
|
2281
|
+
return false; // Exclude generic injected when io.metamask is available
|
|
2282
|
+
}
|
|
2283
|
+
return true;
|
|
2284
|
+
})
|
|
2285
|
+
.map((connector) => ({
|
|
2286
|
+
id: connector.id,
|
|
2287
|
+
name: connector.name,
|
|
2288
|
+
icon: connector.icon, // Pass the icon from the connector
|
|
2289
|
+
}))
|
|
2290
|
+
: [];
|
|
2291
|
+
// Determine the current address (external wallet takes precedence)
|
|
2292
|
+
const currentAddress = isExternalWalletConnected && externalWalletAddress
|
|
2293
|
+
? externalWalletAddress
|
|
2294
|
+
: address;
|
|
2295
|
+
// Determine the current chain ID (external wallet takes precedence)
|
|
2296
|
+
const currentChainId = isExternalWalletConnected && externalWalletChainId
|
|
2297
|
+
? externalWalletChainId
|
|
2298
|
+
: chainId;
|
|
2299
|
+
// Fetch balance for Abstraxn wallet (not external wallet)
|
|
2300
|
+
useEffect(() => {
|
|
2301
|
+
if (isExternalWalletConnected || !address || !currentChainId) {
|
|
2302
|
+
setWalletBalance(null);
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
const fetchBalance = async () => {
|
|
2306
|
+
try {
|
|
2307
|
+
const currentChain = getChainById(currentChainId);
|
|
2308
|
+
if (!currentChain || currentChain.type !== "evm") {
|
|
2309
|
+
setWalletBalance(null);
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
const publicClient = createPublicClient({
|
|
2313
|
+
chain: {
|
|
2314
|
+
id: currentChain.id,
|
|
2315
|
+
name: currentChain.name,
|
|
2316
|
+
nativeCurrency: currentChain.nativeCurrency,
|
|
2317
|
+
rpcUrls: {
|
|
2318
|
+
default: {
|
|
2319
|
+
http: [currentChain.rpcUrl],
|
|
2320
|
+
},
|
|
2321
|
+
},
|
|
2322
|
+
},
|
|
2323
|
+
transport: http(currentChain.rpcUrl),
|
|
2324
|
+
});
|
|
2325
|
+
const balance = await publicClient.getBalance({
|
|
2326
|
+
address: address,
|
|
2327
|
+
});
|
|
2328
|
+
setWalletBalance(balance);
|
|
2329
|
+
}
|
|
2330
|
+
catch (error) {
|
|
2331
|
+
console.error("Failed to fetch balance:", error);
|
|
2332
|
+
setWalletBalance(null);
|
|
2333
|
+
}
|
|
2334
|
+
};
|
|
2335
|
+
fetchBalance();
|
|
2336
|
+
// Refetch balance every 10 seconds
|
|
2337
|
+
const interval = setInterval(fetchBalance, 10000);
|
|
2338
|
+
return () => clearInterval(interval);
|
|
2339
|
+
}, [address, currentChainId, isExternalWalletConnected]);
|
|
2340
|
+
// Compute available chains from config - supports both legacy and new format
|
|
2341
|
+
const availableChains = useMemo(() => {
|
|
2342
|
+
const chains = [];
|
|
2343
|
+
const chainConfig = config.chains;
|
|
2344
|
+
// Priority 1: Check new chains config format
|
|
2345
|
+
if (chainConfig?.supportedEvmChains &&
|
|
2346
|
+
chainConfig.supportedEvmChains.length > 0) {
|
|
2347
|
+
// Use configured chains from new format
|
|
2348
|
+
chainConfig.supportedEvmChains.forEach((chainName) => {
|
|
2349
|
+
const chain = EVM_CHAINS[chainName];
|
|
2350
|
+
if (chain) {
|
|
2351
|
+
chains.push(chain);
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
// Priority 2: Check legacy supportedChains format
|
|
2356
|
+
else if (config.supportedChains && config.supportedChains.length > 0) {
|
|
2357
|
+
// Convert legacy Chain format to ChainData
|
|
2358
|
+
config.supportedChains.forEach((legacyChain) => {
|
|
2359
|
+
// Try to find matching chain by ID
|
|
2360
|
+
const chainData = getChainById(legacyChain.id);
|
|
2361
|
+
if (chainData) {
|
|
2362
|
+
chains.push(chainData);
|
|
2363
|
+
}
|
|
2364
|
+
else {
|
|
2365
|
+
// If not found, create ChainData from legacy chain
|
|
2366
|
+
const newChain = {
|
|
2367
|
+
id: legacyChain.id,
|
|
2368
|
+
name: legacyChain.name.toLowerCase().replace(/\s+/g, "-"),
|
|
2369
|
+
displayName: legacyChain.name,
|
|
2370
|
+
rpcUrl: legacyChain.rpcUrl,
|
|
2371
|
+
explorerUrl: `https://etherscan.io`, // Default explorer
|
|
2372
|
+
nativeCurrency: legacyChain.nativeCurrency,
|
|
2373
|
+
type: "evm", // Assume EVM for legacy chains
|
|
2374
|
+
isTestnet: legacyChain.id !== 1 &&
|
|
2375
|
+
legacyChain.id !== 137 &&
|
|
2376
|
+
legacyChain.id !== 8453, // Common mainnets
|
|
2377
|
+
};
|
|
2378
|
+
chains.push(newChain);
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
// Priority 3: Default chains only if nothing configured at all
|
|
2383
|
+
// Only show defaults if both config.chains and config.supportedChains are missing
|
|
2384
|
+
else if (!chainConfig && !config.supportedChains) {
|
|
2385
|
+
chains.push(EVM_CHAINS.ethereum, EVM_CHAINS.polygon, EVM_CHAINS.base);
|
|
2386
|
+
}
|
|
2387
|
+
// Add Solana chains if enabled (from new config format)
|
|
2388
|
+
if (chainConfig?.enableSolana) {
|
|
2389
|
+
chains.push(SOLANA_CHAINS.solana);
|
|
2390
|
+
}
|
|
2391
|
+
// Remove duplicates by chain ID
|
|
2392
|
+
const uniqueChains = chains.filter((chain, index, self) => index === self.findIndex((c) => c.id === chain.id));
|
|
2393
|
+
// Only return defaults if absolutely nothing was configured
|
|
2394
|
+
return uniqueChains.length > 0
|
|
2395
|
+
? uniqueChains
|
|
2396
|
+
: !chainConfig && !config.supportedChains
|
|
2397
|
+
? [EVM_CHAINS.ethereum, EVM_CHAINS.polygon, EVM_CHAINS.base]
|
|
2398
|
+
: [];
|
|
2399
|
+
}, [config.chains, config.supportedChains]);
|
|
2400
|
+
// Get current chain data
|
|
2401
|
+
const currentChain = useMemo(() => {
|
|
2402
|
+
if (!currentChainId)
|
|
2403
|
+
return null;
|
|
2404
|
+
return getChainById(currentChainId) || null;
|
|
2405
|
+
}, [currentChainId]);
|
|
2406
|
+
// Enhanced switchChain that works for both EVM and Solana
|
|
2407
|
+
const switchChainEnhanced = useCallback(async (targetChainId) => {
|
|
2408
|
+
if (!walletRef.current) {
|
|
2409
|
+
throw new Error("Wallet not initialized");
|
|
2410
|
+
}
|
|
2411
|
+
// Check if chain is in availableChains (user-configured chains)
|
|
2412
|
+
const targetChainInAvailable = availableChains?.find((c) => c.id === targetChainId);
|
|
2413
|
+
if (!targetChainInAvailable) {
|
|
2414
|
+
throw new Error(`Chain with ID ${targetChainId} is not available. Please configure it in your SDK setup.`);
|
|
2415
|
+
}
|
|
2416
|
+
const targetChain = getChainById(targetChainId);
|
|
2417
|
+
if (!targetChain) {
|
|
2418
|
+
throw new Error(`Chain with ID ${targetChainId} is not supported`);
|
|
2419
|
+
}
|
|
2420
|
+
setLoading(true);
|
|
2421
|
+
setError(null);
|
|
2422
|
+
try {
|
|
2423
|
+
if (targetChain.type === "evm") {
|
|
2424
|
+
// For EVM chains, use the existing switchChain
|
|
2425
|
+
if (isExternalWalletConnected && wagmiSwitchChain) {
|
|
2426
|
+
// External wallet - use wagmi switchChain
|
|
2427
|
+
await wagmiSwitchChain.switchChainAsync({ chainId: targetChainId });
|
|
2428
|
+
setExternalWalletChainId(targetChainId);
|
|
2429
|
+
}
|
|
2430
|
+
else {
|
|
2431
|
+
// Abstraxn wallet - try to switch, but if chain is not in wallet's supportedChains,
|
|
2432
|
+
// we'll update the state directly (for chains that are in availableChains but not in wallet config)
|
|
2433
|
+
try {
|
|
2434
|
+
await switchChain(targetChainId);
|
|
2435
|
+
}
|
|
2436
|
+
catch (switchErr) {
|
|
2437
|
+
// If the wallet doesn't support this chain, but it's in availableChains,
|
|
2438
|
+
// we can still update the state to allow UI to work
|
|
2439
|
+
if (switchErr?.message?.includes("not supported") &&
|
|
2440
|
+
targetChainInAvailable) {
|
|
2441
|
+
// Update chainId state directly for chains in availableChains
|
|
2442
|
+
setChainId(targetChainId);
|
|
2443
|
+
// Emit chainChanged event if wallet supports it
|
|
2444
|
+
if (walletRef.current &&
|
|
2445
|
+
typeof walletRef.current.emit === "function") {
|
|
2446
|
+
walletRef.current.emit("chainChanged", targetChainId);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
else {
|
|
2450
|
+
throw switchErr;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
else if (targetChain.type === "solana") {
|
|
2456
|
+
// For Solana, we might need different handling
|
|
2457
|
+
// For now, just update the chainId state
|
|
2458
|
+
// TODO: Implement actual Solana chain switching when wallet supports it
|
|
2459
|
+
setChainId(targetChainId);
|
|
2460
|
+
}
|
|
2461
|
+
// Update chainId state
|
|
2462
|
+
if (!isExternalWalletConnected) {
|
|
2463
|
+
setChainId(targetChainId);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
catch (err) {
|
|
2467
|
+
const error = err instanceof Error
|
|
2468
|
+
? err
|
|
2469
|
+
: new Error(`Failed to switch to chain ${targetChainId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2470
|
+
setError(error);
|
|
2471
|
+
throw error;
|
|
2472
|
+
}
|
|
2473
|
+
finally {
|
|
2474
|
+
setLoading(false);
|
|
2475
|
+
}
|
|
2476
|
+
}, [isExternalWalletConnected, wagmiSwitchChain, switchChain, availableChains]);
|
|
2477
|
+
const value = {
|
|
2478
|
+
wallet: walletRef.current,
|
|
2479
|
+
isInitialized,
|
|
2480
|
+
isConnected: isExternalWalletConnected || isConnected, // True if either wallet is connected
|
|
2481
|
+
address: currentAddress, // Use external wallet address if connected, otherwise Abstraxn wallet address
|
|
2482
|
+
user,
|
|
2483
|
+
whoami,
|
|
2484
|
+
chainId: currentChainId, // Use external wallet chain ID if connected, otherwise Abstraxn wallet chain ID
|
|
2485
|
+
error,
|
|
2486
|
+
loading,
|
|
2487
|
+
init,
|
|
2488
|
+
connect,
|
|
2489
|
+
disconnect,
|
|
2490
|
+
showOnboarding,
|
|
2491
|
+
hideOnboarding,
|
|
2492
|
+
getAddress,
|
|
2493
|
+
getChainId,
|
|
2494
|
+
switchChain,
|
|
2495
|
+
signMessage,
|
|
2496
|
+
signTransaction,
|
|
2497
|
+
sendTransaction,
|
|
2498
|
+
signTransactionViaAPI,
|
|
2499
|
+
signAndSendTransaction,
|
|
2500
|
+
loginWithOTP,
|
|
2501
|
+
verifyOTP,
|
|
2502
|
+
loginWithGoogle,
|
|
2503
|
+
handleGoogleCallback: () => handleGoogleCallback(true),
|
|
2504
|
+
refreshWhoami,
|
|
2505
|
+
uiConfig: config.ui,
|
|
2506
|
+
// External wallet methods
|
|
2507
|
+
connectExternalWallet: externalWalletsEnabled
|
|
2508
|
+
? connectExternalWallet
|
|
2509
|
+
: undefined,
|
|
2510
|
+
disconnectExternalWallet: externalWalletsEnabled
|
|
2511
|
+
? disconnectExternalWallet
|
|
2512
|
+
: undefined,
|
|
2513
|
+
isExternalWalletConnected: externalWalletsEnabled
|
|
2514
|
+
? isExternalWalletConnected
|
|
2515
|
+
: undefined,
|
|
2516
|
+
externalWalletAddress: externalWalletsEnabled
|
|
2517
|
+
? externalWalletAddress
|
|
2518
|
+
: undefined,
|
|
2519
|
+
externalWalletChainId: externalWalletsEnabled
|
|
2520
|
+
? externalWalletChainId
|
|
2521
|
+
: undefined,
|
|
2522
|
+
externalWalletBalance: externalWalletsEnabled &&
|
|
2523
|
+
wagmiBalance?.data &&
|
|
2524
|
+
typeof wagmiBalance.data === "object" &&
|
|
2525
|
+
wagmiBalance.data !== null &&
|
|
2526
|
+
"value" in wagmiBalance.data
|
|
2527
|
+
? wagmiBalance.data.value
|
|
2528
|
+
: undefined,
|
|
2529
|
+
externalWalletNetwork: externalWalletsEnabled && wagmiChainIdHook
|
|
2530
|
+
? getNetworkName(wagmiChainIdHook)
|
|
2531
|
+
: undefined,
|
|
2532
|
+
availableConnectors: externalWalletsEnabled
|
|
2533
|
+
? availableConnectors
|
|
2534
|
+
: undefined,
|
|
2535
|
+
switchExternalWalletChain: externalWalletsEnabled
|
|
2536
|
+
? switchExternalWalletChain
|
|
2537
|
+
: undefined,
|
|
2538
|
+
// Chain management
|
|
2539
|
+
switchChainEnhanced,
|
|
2540
|
+
currentChain,
|
|
2541
|
+
availableChains,
|
|
2542
|
+
// Balance (for Abstraxn wallet)
|
|
2543
|
+
walletBalance: !isExternalWalletConnected ? walletBalance : undefined,
|
|
2544
|
+
// Connection type
|
|
2545
|
+
connectionType,
|
|
2546
|
+
};
|
|
2547
|
+
// Ref to store latest value to avoid dependency issues (defined after value)
|
|
2548
|
+
const valueRef = useRef(value);
|
|
2549
|
+
valueRef.current = value;
|
|
2550
|
+
// Debug log when address changes
|
|
2551
|
+
// useEffect(() => {
|
|
2552
|
+
// console.log('🔍 Context value check:', {
|
|
2553
|
+
// currentAddress,
|
|
2554
|
+
// isConnected: isExternalWalletConnected || isConnected,
|
|
2555
|
+
// isExternalWalletConnected,
|
|
2556
|
+
// externalWalletAddress,
|
|
2557
|
+
// address,
|
|
2558
|
+
// explicitConnection: explicitConnectionRef.current,
|
|
2559
|
+
// });
|
|
2560
|
+
// }, [currentAddress, isExternalWalletConnected, isConnected, externalWalletAddress, address]);
|
|
2561
|
+
// Reusable function to mount external wallets (moved here so it can be used by showOnboarding)
|
|
2562
|
+
const mountExternalWalletsToContainer = useCallback(async (container, onboardingAny) => {
|
|
2563
|
+
if (!container) {
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
// Check if we're on OTP screen - if so, don't show external wallets
|
|
2567
|
+
const isOtpScreen = onboardingAny.otpVerificationScreen &&
|
|
2568
|
+
onboardingAny.otpVerificationScreen.parentElement &&
|
|
2569
|
+
onboardingAny.otpVerificationScreen.offsetParent !== null;
|
|
2570
|
+
if (!isOtpScreen) {
|
|
2571
|
+
// Only show external wallets if not on OTP screen
|
|
2572
|
+
container.style.display = "";
|
|
2573
|
+
// Show divider only if both email/Google AND external wallets are visible
|
|
2574
|
+
const authMethods = onboardingAny.config?.authMethods || [
|
|
2575
|
+
"otp",
|
|
2576
|
+
"google",
|
|
2577
|
+
];
|
|
2578
|
+
const showEmail = authMethods.includes("otp");
|
|
2579
|
+
const showGoogle = authMethods.includes("google");
|
|
2580
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
2581
|
+
if (onboardingAny.externalWalletDivider && hasEmailOrGoogle) {
|
|
2582
|
+
onboardingAny.externalWalletDivider.style.display = "";
|
|
2583
|
+
}
|
|
2584
|
+
else if (onboardingAny.externalWalletDivider) {
|
|
2585
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
else {
|
|
2589
|
+
// On OTP screen - hide external wallets
|
|
2590
|
+
container.style.display = "none";
|
|
2591
|
+
if (onboardingAny.externalWalletDivider) {
|
|
2592
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2593
|
+
}
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
// Check if root exists and container is still in DOM
|
|
2597
|
+
const containerInDOM = container.parentElement !== null;
|
|
2598
|
+
// Check if the container is the same as the one we created the root for
|
|
2599
|
+
const isSameContainer = externalWalletContainerRef.current === container;
|
|
2600
|
+
const rootStillValid = externalWalletRootRef.current && containerInDOM && isSameContainer;
|
|
2601
|
+
const renderExternalWalletButtons = () => (_jsx(AbstraxnContext.Provider, { value: valueRef.current, children: _jsx(ExternalWalletButtons, { onConnect: async () => { }, onShowMoreWallets: () => {
|
|
2602
|
+
const onboardingAny = onboardingRef.current;
|
|
2603
|
+
if (onboardingAny?.emailForm) {
|
|
2604
|
+
onboardingAny.emailForm.style.display = "none";
|
|
2605
|
+
}
|
|
2606
|
+
if (onboardingAny?.googleButton) {
|
|
2607
|
+
onboardingAny.googleButton.style.display = "none";
|
|
2608
|
+
}
|
|
2609
|
+
// Hide all passkey elements when wallet selection screen is shown
|
|
2610
|
+
if (onboardingAny?.passkeyLoginButton) {
|
|
2611
|
+
onboardingAny.passkeyLoginButton.style.display = "none";
|
|
2612
|
+
}
|
|
2613
|
+
if (onboardingAny?.passkeySignupLink) {
|
|
2614
|
+
onboardingAny.passkeySignupLink.style.display = "none";
|
|
2615
|
+
}
|
|
2616
|
+
if (onboardingAny?.passkeyErrorElement) {
|
|
2617
|
+
onboardingAny.passkeyErrorElement.style.display = "none";
|
|
2618
|
+
}
|
|
2619
|
+
if (onboardingAny?.passkeyDivider) {
|
|
2620
|
+
onboardingAny.passkeyDivider.style.display = "none";
|
|
2621
|
+
}
|
|
2622
|
+
if (onboardingAny?.externalWalletDivider) {
|
|
2623
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2624
|
+
}
|
|
2625
|
+
if (onboardingAny?.divider) {
|
|
2626
|
+
onboardingAny.divider.style.display = "none";
|
|
2627
|
+
}
|
|
2628
|
+
}, onHideMoreWallets: () => {
|
|
2629
|
+
const onboardingAny = onboardingRef.current;
|
|
2630
|
+
const authMethods = onboardingAny?.config?.authMethods || [
|
|
2631
|
+
"otp",
|
|
2632
|
+
"google",
|
|
2633
|
+
];
|
|
2634
|
+
const showEmail = authMethods.includes("otp");
|
|
2635
|
+
const showGoogle = authMethods.includes("google");
|
|
2636
|
+
const showPasskey = authMethods.includes("passkey");
|
|
2637
|
+
if (onboardingAny?.emailForm) {
|
|
2638
|
+
onboardingAny.emailForm.style.display = showEmail ? "" : "none";
|
|
2639
|
+
}
|
|
2640
|
+
if (onboardingAny?.googleButton) {
|
|
2641
|
+
onboardingAny.googleButton.style.display = showGoogle
|
|
2642
|
+
? ""
|
|
2643
|
+
: "none";
|
|
2644
|
+
}
|
|
2645
|
+
// Show passkey controls if enabled
|
|
2646
|
+
if (onboardingAny?.passkeyLoginButton) {
|
|
2647
|
+
onboardingAny.passkeyLoginButton.style.display = showPasskey
|
|
2648
|
+
? ""
|
|
2649
|
+
: "none";
|
|
2650
|
+
}
|
|
2651
|
+
if (onboardingAny?.passkeySignupLink) {
|
|
2652
|
+
onboardingAny.passkeySignupLink.style.display = showPasskey
|
|
2653
|
+
? ""
|
|
2654
|
+
: "none";
|
|
2655
|
+
}
|
|
2656
|
+
if (onboardingAny?.passkeyErrorElement) {
|
|
2657
|
+
onboardingAny.passkeyErrorElement.style.display = "none";
|
|
2658
|
+
}
|
|
2659
|
+
// Show passkey divider if passkey is enabled and there are other auth methods
|
|
2660
|
+
if (onboardingAny?.passkeyDivider) {
|
|
2661
|
+
onboardingAny.passkeyDivider.style.display =
|
|
2662
|
+
showPasskey && (showEmail || showGoogle) ? "" : "none";
|
|
2663
|
+
}
|
|
2664
|
+
if (onboardingAny?.divider) {
|
|
2665
|
+
onboardingAny.divider.style.display =
|
|
2666
|
+
showEmail && showGoogle ? "" : "none";
|
|
2667
|
+
}
|
|
2668
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
2669
|
+
if (onboardingAny?.externalWalletDivider && hasEmailOrGoogle) {
|
|
2670
|
+
onboardingAny.externalWalletDivider.style.display = "";
|
|
2671
|
+
}
|
|
2672
|
+
else if (onboardingAny?.externalWalletDivider) {
|
|
2673
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2674
|
+
}
|
|
2675
|
+
} }) }));
|
|
2676
|
+
if (rootStillValid) {
|
|
2677
|
+
externalWalletRootRef.current.render(renderExternalWalletButtons());
|
|
2678
|
+
externalWalletMountedRef.current = true;
|
|
2679
|
+
}
|
|
2680
|
+
else {
|
|
2681
|
+
try {
|
|
2682
|
+
// If we have an old root but the container changed or is invalid, unmount it first
|
|
2683
|
+
if (externalWalletRootRef.current) {
|
|
2684
|
+
try {
|
|
2685
|
+
// Attempt to unmount if possible, though react-dom/client roots don't have unmount in the same way as legacy
|
|
2686
|
+
// But we should at least clear the ref
|
|
2687
|
+
externalWalletRootRef.current.unmount();
|
|
2688
|
+
}
|
|
2689
|
+
catch (e) {
|
|
2690
|
+
// Ignore unmount errors
|
|
2691
|
+
}
|
|
2692
|
+
externalWalletRootRef.current = null;
|
|
2693
|
+
}
|
|
2694
|
+
const reactDomClient = await import("react-dom/client");
|
|
2695
|
+
const rootInstance = reactDomClient.createRoot(container);
|
|
2696
|
+
externalWalletRootRef.current = rootInstance;
|
|
2697
|
+
externalWalletContainerRef.current = container; // Track the container
|
|
2698
|
+
rootInstance.render(renderExternalWalletButtons());
|
|
2699
|
+
externalWalletMountedRef.current = true;
|
|
2700
|
+
}
|
|
2701
|
+
catch (error) {
|
|
2702
|
+
externalWalletMountedRef.current = false;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
}, []);
|
|
2706
|
+
// Mount ExternalWalletButtons into OnboardingUI container (only when wagmi is available)
|
|
2707
|
+
useEffect(() => {
|
|
2708
|
+
if (!externalWalletsEnabled) {
|
|
2709
|
+
// When disabled, ensure container is hidden
|
|
2710
|
+
const onboardingAny = onboardingRef.current;
|
|
2711
|
+
if (onboardingAny?.externalWalletContainer) {
|
|
2712
|
+
onboardingAny.externalWalletContainer.style.display = "none";
|
|
2713
|
+
}
|
|
2714
|
+
if (onboardingAny?.externalWalletDivider) {
|
|
2715
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2716
|
+
}
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
if (!onboardingRef.current) {
|
|
2720
|
+
console.log("OnboardingUI not initialized yet");
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
if (!wagmiConnect) {
|
|
2724
|
+
console.log("Wagmi connect not available");
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
if (!wagmiConnect.connectors || wagmiConnect.connectors.length === 0) {
|
|
2728
|
+
console.log("Wagmi connectors not ready yet");
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
if (availableConnectors.length === 0) {
|
|
2732
|
+
// console.log('No available connectors');
|
|
2733
|
+
// Don't return early - still mount the component so it can update when connectors become available
|
|
2734
|
+
// return;
|
|
2735
|
+
}
|
|
2736
|
+
let retryCount = 0;
|
|
2737
|
+
const maxRetries = 50; // Try for up to 2.5 seconds
|
|
2738
|
+
const mountExternalWallets = async () => {
|
|
2739
|
+
const onboardingAny = onboardingRef.current;
|
|
2740
|
+
if (!onboardingAny?.externalWalletContainer) {
|
|
2741
|
+
retryCount++;
|
|
2742
|
+
if (retryCount < maxRetries) {
|
|
2743
|
+
// Retry after a short delay if container not ready
|
|
2744
|
+
setTimeout(mountExternalWallets, 50); // Reduced from 100ms to 50ms
|
|
2745
|
+
}
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
try {
|
|
2749
|
+
const container = onboardingAny.externalWalletContainer;
|
|
2750
|
+
if (!container) {
|
|
2751
|
+
console.warn("External wallet container is null");
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
// Check if we're on OTP screen - if so, don't show external wallets
|
|
2755
|
+
// Check if OTP screen exists AND is actually visible in the DOM
|
|
2756
|
+
const isOtpScreen = onboardingAny.otpVerificationScreen &&
|
|
2757
|
+
onboardingAny.otpVerificationScreen.parentElement &&
|
|
2758
|
+
onboardingAny.otpVerificationScreen.offsetParent !== null;
|
|
2759
|
+
if (!isOtpScreen) {
|
|
2760
|
+
// Only show external wallets if not on OTP screen
|
|
2761
|
+
// Force container to be visible when external wallets are enabled
|
|
2762
|
+
container.style.display = "";
|
|
2763
|
+
// Show divider only if both email/Google AND external wallets are visible
|
|
2764
|
+
const authMethods = onboardingAny.config?.authMethods || [
|
|
2765
|
+
"otp",
|
|
2766
|
+
"google",
|
|
2767
|
+
];
|
|
2768
|
+
const showEmail = authMethods.includes("otp");
|
|
2769
|
+
const showGoogle = authMethods.includes("google");
|
|
2770
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
2771
|
+
if (onboardingAny.externalWalletDivider && hasEmailOrGoogle) {
|
|
2772
|
+
onboardingAny.externalWalletDivider.style.display = "";
|
|
2773
|
+
}
|
|
2774
|
+
else if (onboardingAny.externalWalletDivider) {
|
|
2775
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
else {
|
|
2779
|
+
// On OTP screen - hide external wallets
|
|
2780
|
+
container.style.display = "none";
|
|
2781
|
+
if (onboardingAny.externalWalletDivider) {
|
|
2782
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
// Reuse existing root if container matches to avoid unmount/remount race conditions
|
|
2786
|
+
let rootInstance = externalWalletRootRef.current;
|
|
2787
|
+
const reactDomClient = await import("react-dom/client");
|
|
2788
|
+
if (rootInstance && externalWalletContainerRef.current === container) {
|
|
2789
|
+
// Reuse existing root - no need to unmount/remount
|
|
2790
|
+
}
|
|
2791
|
+
else {
|
|
2792
|
+
// Unmount previous root if it exists (and container is different)
|
|
2793
|
+
if (rootInstance) {
|
|
2794
|
+
try {
|
|
2795
|
+
// Wrap unmount in setTimeout to avoid "synchronously unmount... while rendering" error
|
|
2796
|
+
// This happens if unmount is called during a render cycle
|
|
2797
|
+
const oldRoot = rootInstance;
|
|
2798
|
+
setTimeout(() => {
|
|
2799
|
+
try {
|
|
2800
|
+
oldRoot.unmount();
|
|
2801
|
+
}
|
|
2802
|
+
catch (e) {
|
|
2803
|
+
// Ignore
|
|
2804
|
+
}
|
|
2805
|
+
}, 0);
|
|
2806
|
+
}
|
|
2807
|
+
catch (e) {
|
|
2808
|
+
// Ignore unmount errors
|
|
2809
|
+
}
|
|
2810
|
+
externalWalletRootRef.current = null;
|
|
2811
|
+
externalWalletContainerRef.current = null;
|
|
2812
|
+
}
|
|
2813
|
+
// Create new root
|
|
2814
|
+
rootInstance = reactDomClient.createRoot(container);
|
|
2815
|
+
externalWalletRootRef.current = rootInstance;
|
|
2816
|
+
externalWalletContainerRef.current = container; // Track the container
|
|
2817
|
+
}
|
|
2818
|
+
// Render the ExternalWalletButtons component
|
|
2819
|
+
rootInstance.render(_jsx(AbstraxnContext.Provider, { value: valueRef.current, children: _jsx(ExternalWalletButtons, { onConnect: async (_connectorId, _address) => {
|
|
2820
|
+
// console.log('External wallet connected:', connectorId, address);
|
|
2821
|
+
// Modal will be closed automatically by the useEffect that watches wagmiAccount
|
|
2822
|
+
}, onShowMoreWallets: () => {
|
|
2823
|
+
// Hide email and Google sections when "More wallets" is clicked
|
|
2824
|
+
const onboardingAny = onboardingRef.current;
|
|
2825
|
+
if (onboardingAny?.emailForm) {
|
|
2826
|
+
onboardingAny.emailForm.style.display = "none";
|
|
2827
|
+
}
|
|
2828
|
+
if (onboardingAny?.googleButton) {
|
|
2829
|
+
onboardingAny.googleButton.style.display = "none";
|
|
2830
|
+
}
|
|
2831
|
+
// Hide all passkey elements when wallet selection screen is shown
|
|
2832
|
+
if (onboardingAny?.passkeyLoginButton) {
|
|
2833
|
+
onboardingAny.passkeyLoginButton.style.display = "none";
|
|
2834
|
+
}
|
|
2835
|
+
if (onboardingAny?.passkeySignupLink) {
|
|
2836
|
+
onboardingAny.passkeySignupLink.style.display = "none";
|
|
2837
|
+
}
|
|
2838
|
+
if (onboardingAny?.passkeyErrorElement) {
|
|
2839
|
+
onboardingAny.passkeyErrorElement.style.display = "none";
|
|
2840
|
+
}
|
|
2841
|
+
if (onboardingAny?.passkeyDivider) {
|
|
2842
|
+
onboardingAny.passkeyDivider.style.display = "none";
|
|
2843
|
+
}
|
|
2844
|
+
if (onboardingAny?.externalWalletDivider) {
|
|
2845
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2846
|
+
}
|
|
2847
|
+
// Also hide the divider between email and Google if it exists
|
|
2848
|
+
if (onboardingAny?.divider) {
|
|
2849
|
+
onboardingAny.divider.style.display = "none";
|
|
2850
|
+
}
|
|
2851
|
+
}, onHideMoreWallets: () => {
|
|
2852
|
+
// Show email and Google sections when back is clicked (restore initial screen)
|
|
2853
|
+
const onboardingAny = onboardingRef.current;
|
|
2854
|
+
const authMethods = onboardingAny?.config?.authMethods || [
|
|
2855
|
+
"otp",
|
|
2856
|
+
"google",
|
|
2857
|
+
];
|
|
2858
|
+
const showEmail = authMethods.includes("otp");
|
|
2859
|
+
const showGoogle = authMethods.includes("google");
|
|
2860
|
+
const showPasskey = authMethods.includes("passkey");
|
|
2861
|
+
// Show email form if enabled
|
|
2862
|
+
if (onboardingAny?.emailForm) {
|
|
2863
|
+
onboardingAny.emailForm.style.display = showEmail
|
|
2864
|
+
? ""
|
|
2865
|
+
: "none";
|
|
2866
|
+
}
|
|
2867
|
+
// Show Google button if enabled
|
|
2868
|
+
if (onboardingAny?.googleButton) {
|
|
2869
|
+
onboardingAny.googleButton.style.display = showGoogle
|
|
2870
|
+
? ""
|
|
2871
|
+
: "none";
|
|
2872
|
+
}
|
|
2873
|
+
// Show passkey controls if enabled
|
|
2874
|
+
if (onboardingAny?.passkeyLoginButton) {
|
|
2875
|
+
onboardingAny.passkeyLoginButton.style.display = showPasskey
|
|
2876
|
+
? ""
|
|
2877
|
+
: "none";
|
|
2878
|
+
}
|
|
2879
|
+
if (onboardingAny?.passkeySignupLink) {
|
|
2880
|
+
onboardingAny.passkeySignupLink.style.display = showPasskey
|
|
2881
|
+
? ""
|
|
2882
|
+
: "none";
|
|
2883
|
+
}
|
|
2884
|
+
if (onboardingAny?.passkeyErrorElement) {
|
|
2885
|
+
onboardingAny.passkeyErrorElement.style.display = "none";
|
|
2886
|
+
}
|
|
2887
|
+
// Show passkey divider if passkey is enabled and there are other auth methods
|
|
2888
|
+
if (onboardingAny?.passkeyDivider) {
|
|
2889
|
+
onboardingAny.passkeyDivider.style.display =
|
|
2890
|
+
showPasskey && (showEmail || showGoogle) ? "" : "none";
|
|
2891
|
+
}
|
|
2892
|
+
// Show divider between email and Google if both are enabled
|
|
2893
|
+
if (onboardingAny?.divider) {
|
|
2894
|
+
onboardingAny.divider.style.display =
|
|
2895
|
+
showEmail && showGoogle ? "" : "none";
|
|
2896
|
+
}
|
|
2897
|
+
// Show the "or" divider before external wallets only if email/Google are visible
|
|
2898
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
2899
|
+
if (onboardingAny?.externalWalletDivider && hasEmailOrGoogle) {
|
|
2900
|
+
onboardingAny.externalWalletDivider.style.display = "";
|
|
2901
|
+
}
|
|
2902
|
+
else if (onboardingAny?.externalWalletDivider) {
|
|
2903
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2904
|
+
}
|
|
2905
|
+
} }) }));
|
|
2906
|
+
externalWalletMountedRef.current = true;
|
|
2907
|
+
}
|
|
2908
|
+
catch (error) {
|
|
2909
|
+
console.error("Failed to mount ExternalWalletButtons:", error);
|
|
2910
|
+
externalWalletMountedRef.current = false;
|
|
2911
|
+
}
|
|
2912
|
+
};
|
|
2913
|
+
// When externalWalletsEnabled changes, force a fresh mount
|
|
2914
|
+
// This ensures buttons appear immediately without requiring a refresh
|
|
2915
|
+
const forceRemount = () => {
|
|
2916
|
+
// Unmount existing root if it exists
|
|
2917
|
+
if (externalWalletRootRef.current) {
|
|
2918
|
+
try {
|
|
2919
|
+
externalWalletRootRef.current.unmount();
|
|
2920
|
+
}
|
|
2921
|
+
catch (e) {
|
|
2922
|
+
// Ignore unmount errors
|
|
2923
|
+
}
|
|
2924
|
+
externalWalletRootRef.current = null;
|
|
2925
|
+
}
|
|
2926
|
+
externalWalletContainerRef.current = null;
|
|
2927
|
+
externalWalletMountedRef.current = false;
|
|
2928
|
+
};
|
|
2929
|
+
// Start mounting immediately - no delay needed
|
|
2930
|
+
mountExternalWallets();
|
|
2931
|
+
return () => {
|
|
2932
|
+
// Cleanup if needed
|
|
2933
|
+
};
|
|
2934
|
+
}, [
|
|
2935
|
+
externalWalletsEnabled,
|
|
2936
|
+
wagmiConnect,
|
|
2937
|
+
wagmiConnect?.connectors,
|
|
2938
|
+
availableConnectors.length,
|
|
2939
|
+
]);
|
|
2940
|
+
// Update the rendered component when value changes (but don't re-mount)
|
|
2941
|
+
useEffect(() => {
|
|
2942
|
+
if (!externalWalletsEnabled)
|
|
2943
|
+
return;
|
|
2944
|
+
if (!externalWalletRootRef.current || !externalWalletMountedRef.current)
|
|
2945
|
+
return;
|
|
2946
|
+
// Update the rendered component with latest value
|
|
2947
|
+
try {
|
|
2948
|
+
externalWalletRootRef.current.render(_jsx(AbstraxnContext.Provider, { value: valueRef.current, children: _jsx(ExternalWalletButtons, { onConnect: async (_connectorId, _address) => {
|
|
2949
|
+
// console.log('External wallet connected:', connectorId, address);
|
|
2950
|
+
}, onShowMoreWallets: () => {
|
|
2951
|
+
// Hide email and Google sections when "More wallets" is clicked
|
|
2952
|
+
const onboardingAny = onboardingRef.current;
|
|
2953
|
+
if (onboardingAny?.emailForm) {
|
|
2954
|
+
onboardingAny.emailForm.style.display = "none";
|
|
2955
|
+
}
|
|
2956
|
+
if (onboardingAny?.googleButton) {
|
|
2957
|
+
onboardingAny.googleButton.style.display = "none";
|
|
2958
|
+
}
|
|
2959
|
+
// Hide Discord and X (Twitter) buttons when wallet selection screen is shown
|
|
2960
|
+
if (onboardingAny?.discordButton) {
|
|
2961
|
+
onboardingAny.discordButton.style.display = "none";
|
|
2962
|
+
}
|
|
2963
|
+
if (onboardingAny?.twitterButton) {
|
|
2964
|
+
onboardingAny.twitterButton.style.display = "none";
|
|
2965
|
+
}
|
|
2966
|
+
// Hide social grid if it exists
|
|
2967
|
+
if (onboardingAny?.socialGrid) {
|
|
2968
|
+
onboardingAny.socialGrid.style.display = "none";
|
|
2969
|
+
}
|
|
2970
|
+
// Extra guard: hide any social icon buttons by class
|
|
2971
|
+
if (onboardingAny?.rootElement) {
|
|
2972
|
+
onboardingAny.rootElement
|
|
2973
|
+
.querySelectorAll(".onboarding-button-social-icon, .onboarding-button-social")
|
|
2974
|
+
.forEach((el) => {
|
|
2975
|
+
el.style.display = "none";
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
// Hide all passkey elements when wallet selection screen is shown
|
|
2979
|
+
if (onboardingAny?.passkeyLoginButton) {
|
|
2980
|
+
onboardingAny.passkeyLoginButton.style.display = "none";
|
|
2981
|
+
}
|
|
2982
|
+
if (onboardingAny?.passkeySignupLink) {
|
|
2983
|
+
onboardingAny.passkeySignupLink.style.display = "none";
|
|
2984
|
+
}
|
|
2985
|
+
if (onboardingAny?.passkeyErrorElement) {
|
|
2986
|
+
onboardingAny.passkeyErrorElement.style.display = "none";
|
|
2987
|
+
}
|
|
2988
|
+
if (onboardingAny?.passkeyDivider) {
|
|
2989
|
+
onboardingAny.passkeyDivider.style.display = "none";
|
|
2990
|
+
}
|
|
2991
|
+
if (onboardingAny?.externalWalletDivider) {
|
|
2992
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
2993
|
+
}
|
|
2994
|
+
// Also hide the divider between email and Google if it exists
|
|
2995
|
+
if (onboardingAny?.divider) {
|
|
2996
|
+
onboardingAny.divider.style.display = "none";
|
|
2997
|
+
}
|
|
2998
|
+
}, onHideMoreWallets: () => {
|
|
2999
|
+
// Show email and Google sections when back is clicked
|
|
3000
|
+
const onboardingAny = onboardingRef.current;
|
|
3001
|
+
const authMethods = onboardingAny?.config?.authMethods || [
|
|
3002
|
+
"otp",
|
|
3003
|
+
"google",
|
|
3004
|
+
];
|
|
3005
|
+
const showEmail = authMethods.includes("otp");
|
|
3006
|
+
const showGoogle = authMethods.includes("google");
|
|
3007
|
+
const showTwitter = authMethods.includes("twitter");
|
|
3008
|
+
const showDiscord = authMethods.includes("discord");
|
|
3009
|
+
const showPasskey = authMethods.includes("passkey");
|
|
3010
|
+
if (onboardingAny?.emailForm) {
|
|
3011
|
+
onboardingAny.emailForm.style.display = showEmail ? "" : "none";
|
|
3012
|
+
}
|
|
3013
|
+
if (onboardingAny?.googleButton) {
|
|
3014
|
+
onboardingAny.googleButton.style.display = showGoogle
|
|
3015
|
+
? ""
|
|
3016
|
+
: "none";
|
|
3017
|
+
}
|
|
3018
|
+
// Show Discord and X (Twitter) buttons if enabled
|
|
3019
|
+
if (onboardingAny?.discordButton) {
|
|
3020
|
+
onboardingAny.discordButton.style.display = showDiscord
|
|
3021
|
+
? ""
|
|
3022
|
+
: "none";
|
|
3023
|
+
}
|
|
3024
|
+
if (onboardingAny?.twitterButton) {
|
|
3025
|
+
onboardingAny.twitterButton.style.display = showTwitter
|
|
3026
|
+
? ""
|
|
3027
|
+
: "none";
|
|
3028
|
+
}
|
|
3029
|
+
// Show social grid if it exists and social methods are enabled
|
|
3030
|
+
if (onboardingAny?.socialGrid) {
|
|
3031
|
+
const hasSocialMethods = showGoogle || showTwitter || showDiscord;
|
|
3032
|
+
onboardingAny.socialGrid.style.display = hasSocialMethods
|
|
3033
|
+
? ""
|
|
3034
|
+
: "none";
|
|
3035
|
+
}
|
|
3036
|
+
// Extra guard: restore social icon buttons by class based on flags
|
|
3037
|
+
if (onboardingAny?.rootElement) {
|
|
3038
|
+
const hasSocialMethods = showGoogle || showTwitter || showDiscord;
|
|
3039
|
+
onboardingAny.rootElement
|
|
3040
|
+
.querySelectorAll(".onboarding-button-social-icon, .onboarding-button-social")
|
|
3041
|
+
.forEach((el) => {
|
|
3042
|
+
el.style.display = hasSocialMethods
|
|
3043
|
+
? ""
|
|
3044
|
+
: "none";
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
// Show passkey controls if enabled
|
|
3048
|
+
if (onboardingAny?.passkeyLoginButton) {
|
|
3049
|
+
onboardingAny.passkeyLoginButton.style.display = showPasskey
|
|
3050
|
+
? ""
|
|
3051
|
+
: "none";
|
|
3052
|
+
}
|
|
3053
|
+
if (onboardingAny?.passkeySignupLink) {
|
|
3054
|
+
onboardingAny.passkeySignupLink.style.display = showPasskey
|
|
3055
|
+
? ""
|
|
3056
|
+
: "none";
|
|
3057
|
+
}
|
|
3058
|
+
if (onboardingAny?.passkeyErrorElement) {
|
|
3059
|
+
onboardingAny.passkeyErrorElement.style.display = "none";
|
|
3060
|
+
}
|
|
3061
|
+
// Show passkey divider if passkey is enabled and there are other auth methods
|
|
3062
|
+
if (onboardingAny?.passkeyDivider) {
|
|
3063
|
+
onboardingAny.passkeyDivider.style.display =
|
|
3064
|
+
showPasskey &&
|
|
3065
|
+
(showEmail || showGoogle || showTwitter || showDiscord)
|
|
3066
|
+
? ""
|
|
3067
|
+
: "none";
|
|
3068
|
+
}
|
|
3069
|
+
// Show divider between email and social methods if both are enabled
|
|
3070
|
+
if (onboardingAny?.divider) {
|
|
3071
|
+
const hasSocialMethods = showGoogle || showTwitter || showDiscord;
|
|
3072
|
+
onboardingAny.divider.style.display =
|
|
3073
|
+
showEmail && hasSocialMethods ? "" : "none";
|
|
3074
|
+
}
|
|
3075
|
+
if (onboardingAny?.externalWalletDivider) {
|
|
3076
|
+
onboardingAny.externalWalletDivider.style.display = "";
|
|
3077
|
+
}
|
|
3078
|
+
} }) }));
|
|
3079
|
+
}
|
|
3080
|
+
catch (error) {
|
|
3081
|
+
console.warn("Failed to update ExternalWalletButtons:", error);
|
|
3082
|
+
}
|
|
3083
|
+
}, [value, externalWalletsEnabled]);
|
|
3084
|
+
return (_jsx(AbstraxnContext.Provider, { value: value, children: children }));
|
|
3085
|
+
}
|
|
3086
|
+
//# sourceMappingURL=AbstraxnProviderInner.js.map
|