signer-test-sdk-react 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/AbstraxnProvider.js +361 -32
- package/dist/src/AbstraxnProvider.js.map +1 -1
- package/dist/src/WalletModal.css +1438 -14
- package/dist/src/WalletModal.d.ts +2 -1
- package/dist/src/WalletModal.js +249 -13
- package/dist/src/WalletModal.js.map +1 -1
- package/dist/src/chains.d.ts +55 -0
- package/dist/src/chains.js +221 -0
- package/dist/src/chains.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js +24 -3
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +4 -2
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +97 -16
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -1
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js +1 -1
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js.map +1 -1
- package/dist/src/components/QRCode.d.ts +13 -0
- package/dist/src/components/QRCode.js +6 -0
- package/dist/src/components/QRCode.js.map +1 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +29 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install @abstraxn/signer-react
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
**Requirements:**
|
|
14
|
-
- React 18.0.0 or higher
|
|
14
|
+
- React 18.0.0 or higher (supports both React 18 and React 19)
|
|
15
15
|
- Node.js 16.0.0 or higher
|
|
16
16
|
- `@abstraxn/signer-core` (installed automatically as a dependency)
|
|
17
17
|
|
|
@@ -9,8 +9,9 @@ import { OnboardingUIWeb } from './components/OnboardingUI';
|
|
|
9
9
|
import { WagmiProvider, useAccount, useConnect, useDisconnect, useSignMessage, useSendTransaction, useSwitchChain, useBalance, useChainId } from 'wagmi';
|
|
10
10
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
11
11
|
import { createWagmiConfig } from './wagmiConfig';
|
|
12
|
-
import { parseEther } from 'viem';
|
|
12
|
+
import { parseEther, createPublicClient, http } from 'viem';
|
|
13
13
|
import { ExternalWalletButtons } from './ExternalWalletButtons';
|
|
14
|
+
import { EVM_CHAINS, SOLANA_CHAINS, getChainById, toCoreChain } from './chains';
|
|
14
15
|
export const AbstraxnContext = createContext(null);
|
|
15
16
|
// Create a default QueryClient for wagmi
|
|
16
17
|
const defaultQueryClient = new QueryClient({
|
|
@@ -31,6 +32,7 @@ function useAbstraxnProviderBase(_config) {
|
|
|
31
32
|
const [chainId, setChainId] = useState(null);
|
|
32
33
|
const [error, setError] = useState(null);
|
|
33
34
|
const [loading, setLoading] = useState(false);
|
|
35
|
+
const [walletBalance, setWalletBalance] = useState(null);
|
|
34
36
|
const onboardingRef = useRef(null);
|
|
35
37
|
const otpIdRef = useRef(null);
|
|
36
38
|
const walletRef = useRef(null);
|
|
@@ -46,6 +48,7 @@ function useAbstraxnProviderBase(_config) {
|
|
|
46
48
|
chainId, setChainId,
|
|
47
49
|
error, setError,
|
|
48
50
|
loading, setLoading,
|
|
51
|
+
walletBalance, setWalletBalance,
|
|
49
52
|
onboardingRef, otpIdRef, walletRef, googleCallbackHandledRef, twitterCallbackHandledRef, discordCallbackHandledRef,
|
|
50
53
|
};
|
|
51
54
|
}
|
|
@@ -83,7 +86,7 @@ function AbstraxnProviderWithoutWagmi({ config, children }) {
|
|
|
83
86
|
return _jsx(AbstraxnProviderInner, { config: config, children: children, base: base, wagmi: null });
|
|
84
87
|
}
|
|
85
88
|
function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
86
|
-
const { isInitialized, setIsInitialized, isConnected, setIsConnected, address, setAddress, user, setUser, whoami, setWhoami, chainId, setChainId, error, setError, loading, setLoading, onboardingRef, otpIdRef, walletRef, googleCallbackHandledRef, twitterCallbackHandledRef, discordCallbackHandledRef, } = base;
|
|
89
|
+
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;
|
|
87
90
|
const externalWalletsEnabled = config.externalWallets?.enabled ?? false;
|
|
88
91
|
// Keep a ref to avoid re-creating callbacks when toggling config (prevents flicker)
|
|
89
92
|
const externalWalletsEnabledRef = useRef(externalWalletsEnabled);
|
|
@@ -150,12 +153,47 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
150
153
|
if (walletRef.current) {
|
|
151
154
|
return;
|
|
152
155
|
}
|
|
156
|
+
// Compute supported chains from chain config (outside useMemo since we're in useEffect)
|
|
157
|
+
// This should include ALL chains that might be shown in the UI (availableChains)
|
|
158
|
+
// We use the same logic as availableChains to ensure consistency
|
|
159
|
+
let computedSupportedChains = undefined;
|
|
160
|
+
if (config.supportedChains) {
|
|
161
|
+
// Use explicitly provided chains
|
|
162
|
+
computedSupportedChains = config.supportedChains;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Compute from chains config - use same logic as availableChains
|
|
166
|
+
const chainConfig = config.chains;
|
|
167
|
+
const chains = [];
|
|
168
|
+
// Priority 1: Check new chains config format
|
|
169
|
+
if (chainConfig?.supportedEvmChains && chainConfig.supportedEvmChains.length > 0) {
|
|
170
|
+
chainConfig.supportedEvmChains.forEach(chainName => {
|
|
171
|
+
const chainData = EVM_CHAINS[chainName];
|
|
172
|
+
if (chainData && chainData.type === 'evm') {
|
|
173
|
+
chains.push(toCoreChain(chainData));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// Priority 2: If no specific config, include default chains that might be shown
|
|
178
|
+
// This ensures Base and other common chains are available even if not explicitly configured
|
|
179
|
+
if (chains.length === 0) {
|
|
180
|
+
// Include common chains that are likely to be in availableChains
|
|
181
|
+
chains.push(toCoreChain(EVM_CHAINS.ethereum));
|
|
182
|
+
chains.push(toCoreChain(EVM_CHAINS.polygon));
|
|
183
|
+
chains.push(toCoreChain(EVM_CHAINS.base));
|
|
184
|
+
}
|
|
185
|
+
computedSupportedChains = chains.length > 0 ? chains : [
|
|
186
|
+
toCoreChain(EVM_CHAINS.ethereum),
|
|
187
|
+
toCoreChain(EVM_CHAINS.polygon),
|
|
188
|
+
toCoreChain(EVM_CHAINS.base),
|
|
189
|
+
];
|
|
190
|
+
}
|
|
153
191
|
const walletInstance = new AbstraxnWallet({
|
|
154
192
|
apiKey: config.apiKey,
|
|
155
193
|
authMethods: config.authMethods,
|
|
156
194
|
googleClientId: config.googleClientId,
|
|
157
|
-
defaultChainId: config.defaultChainId,
|
|
158
|
-
supportedChains:
|
|
195
|
+
defaultChainId: config.chains?.defaultChainId || config.defaultChainId,
|
|
196
|
+
supportedChains: computedSupportedChains,
|
|
159
197
|
autoConnect: config.autoConnect ?? false,
|
|
160
198
|
enableLogging: config.enableLogging ?? false,
|
|
161
199
|
});
|
|
@@ -187,6 +225,24 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
187
225
|
catch (err) {
|
|
188
226
|
console.error('Error getting wallet info on connect:', err);
|
|
189
227
|
}
|
|
228
|
+
// Ensure loading modal is closed when connected (whoami received)
|
|
229
|
+
if (onboardingRef.current) {
|
|
230
|
+
const onboardingAny = onboardingRef.current;
|
|
231
|
+
if (onboardingAny.hideLoadingModal) {
|
|
232
|
+
onboardingAny.hideLoadingModal();
|
|
233
|
+
}
|
|
234
|
+
// Also ensure the overlay is hidden if it was opened
|
|
235
|
+
if (onboardingAny.modalOverlay && onboardingAny.modalOverlay.classList.contains('onboarding-modal-open')) {
|
|
236
|
+
onboardingAny.modalOverlay.classList.remove('onboarding-modal-open');
|
|
237
|
+
onboardingAny.modalOverlay.classList.add('onboarding-modal-closing');
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
if (onboardingAny.modalOverlay) {
|
|
240
|
+
onboardingAny.modalOverlay.style.display = 'none';
|
|
241
|
+
}
|
|
242
|
+
}, 200);
|
|
243
|
+
document.body.style.overflow = '';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
190
246
|
});
|
|
191
247
|
walletInstance.on('disconnect', () => {
|
|
192
248
|
setIsConnected(false);
|
|
@@ -250,7 +306,9 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
250
306
|
}
|
|
251
307
|
catch (err) {
|
|
252
308
|
console.error('OTP Verify Error:', err);
|
|
253
|
-
|
|
309
|
+
// Return the actual error message from the API
|
|
310
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to verify OTP';
|
|
311
|
+
return { success: false, error: errorMessage };
|
|
254
312
|
}
|
|
255
313
|
},
|
|
256
314
|
onGoogleLogin: async () => {
|
|
@@ -294,22 +352,6 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
294
352
|
onLoginSuccess: async (data) => {
|
|
295
353
|
// Clear any previous errors on successful login
|
|
296
354
|
setError(null);
|
|
297
|
-
// Hide onboarding UI
|
|
298
|
-
if (onboardingRef.current) {
|
|
299
|
-
const onboardingAny = onboardingRef.current;
|
|
300
|
-
if (onboardingAny.modalOverlay) {
|
|
301
|
-
onboardingAny.modalOverlay.classList.remove('onboarding-modal-open');
|
|
302
|
-
onboardingAny.modalOverlay.classList.add('onboarding-modal-closing');
|
|
303
|
-
setTimeout(() => {
|
|
304
|
-
if (onboardingAny.modalOverlay) {
|
|
305
|
-
onboardingAny.modalOverlay.style.display = 'none';
|
|
306
|
-
}
|
|
307
|
-
}, 200);
|
|
308
|
-
}
|
|
309
|
-
document.body.style.overflow = '';
|
|
310
|
-
}
|
|
311
|
-
// Set connected state
|
|
312
|
-
setIsConnected(true);
|
|
313
355
|
// Set user if provided in data
|
|
314
356
|
if (data.user) {
|
|
315
357
|
setUser(data.user);
|
|
@@ -323,9 +365,45 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
323
365
|
setAddress(addr);
|
|
324
366
|
const cid = await walletInstance.getChainId();
|
|
325
367
|
setChainId(cid);
|
|
368
|
+
// Only hide onboarding UI and set connected if whoami succeeds
|
|
369
|
+
if (onboardingRef.current) {
|
|
370
|
+
const onboardingAny = onboardingRef.current;
|
|
371
|
+
if (onboardingAny.hideLoadingModal) {
|
|
372
|
+
onboardingAny.hideLoadingModal();
|
|
373
|
+
}
|
|
374
|
+
if (onboardingAny.modalOverlay) {
|
|
375
|
+
onboardingAny.modalOverlay.classList.remove('onboarding-modal-open');
|
|
376
|
+
onboardingAny.modalOverlay.classList.add('onboarding-modal-closing');
|
|
377
|
+
setTimeout(() => {
|
|
378
|
+
if (onboardingAny.modalOverlay) {
|
|
379
|
+
onboardingAny.modalOverlay.style.display = 'none';
|
|
380
|
+
}
|
|
381
|
+
}, 200);
|
|
382
|
+
}
|
|
383
|
+
document.body.style.overflow = '';
|
|
384
|
+
}
|
|
385
|
+
// Set connected state only after whoami succeeds
|
|
386
|
+
setIsConnected(true);
|
|
326
387
|
}
|
|
327
388
|
catch (err) {
|
|
328
389
|
console.error('Error loading whoami after login:', err);
|
|
390
|
+
// Hide loading modal on whoami error
|
|
391
|
+
if (onboardingRef.current) {
|
|
392
|
+
const onboardingAny = onboardingRef.current;
|
|
393
|
+
if (onboardingAny.hideLoadingModal) {
|
|
394
|
+
onboardingAny.hideLoadingModal();
|
|
395
|
+
}
|
|
396
|
+
// Show error modal/screen
|
|
397
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to load user information';
|
|
398
|
+
if (onboardingAny.showError) {
|
|
399
|
+
onboardingAny.showError(errorMessage);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Set error state
|
|
403
|
+
const error = err instanceof Error ? err : new Error('Failed to load user information after login');
|
|
404
|
+
setError(error);
|
|
405
|
+
// Don't set connected if whoami fails
|
|
406
|
+
setIsConnected(false);
|
|
329
407
|
}
|
|
330
408
|
}
|
|
331
409
|
},
|
|
@@ -347,6 +425,20 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
347
425
|
params.get('error') ||
|
|
348
426
|
params.get('code') ||
|
|
349
427
|
params.get('accessToken');
|
|
428
|
+
const getAuthProviderFromToken = (token) => {
|
|
429
|
+
try {
|
|
430
|
+
const base64Url = token.split('.')[1];
|
|
431
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
432
|
+
const jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) {
|
|
433
|
+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
434
|
+
}).join(''));
|
|
435
|
+
const payload = JSON.parse(jsonPayload);
|
|
436
|
+
return (payload.authProvider || payload.provider || '').toLowerCase();
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
350
442
|
const matchesProvider = (provider, params) => {
|
|
351
443
|
const providerParam = (params.get('provider') || params.get('authProvider') || '').toLowerCase();
|
|
352
444
|
const path = window.location.pathname.toLowerCase();
|
|
@@ -356,6 +448,17 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
356
448
|
}
|
|
357
449
|
return providerParam === provider;
|
|
358
450
|
}
|
|
451
|
+
// Check accessToken for provider
|
|
452
|
+
const accessToken = params.get('accessToken');
|
|
453
|
+
if (accessToken) {
|
|
454
|
+
const tokenProvider = getAuthProviderFromToken(accessToken);
|
|
455
|
+
if (tokenProvider) {
|
|
456
|
+
if (provider === 'twitter') {
|
|
457
|
+
return tokenProvider === 'twitter' || tokenProvider === 'x';
|
|
458
|
+
}
|
|
459
|
+
return tokenProvider === provider;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
359
462
|
if (provider === 'twitter') {
|
|
360
463
|
return path.includes('/twitter') || path.includes('/x');
|
|
361
464
|
}
|
|
@@ -375,12 +478,16 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
375
478
|
onboardingAny.modalOverlay.style.display = 'none';
|
|
376
479
|
document.body.style.overflow = '';
|
|
377
480
|
}
|
|
378
|
-
else if (hasSuccess
|
|
379
|
-
// If success=true is in URL,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
481
|
+
else if (hasSuccess) {
|
|
482
|
+
// If success=true is in URL, show loading modal
|
|
483
|
+
// Use setTimeout to ensure onboarding is fully initialized
|
|
484
|
+
setTimeout(() => {
|
|
485
|
+
const onboardingInstance = onboardingRef.current;
|
|
486
|
+
if (onboardingInstance && onboardingInstance.showLoadingModal) {
|
|
487
|
+
// Directly show loading modal - it creates its own overlay with header and footer
|
|
488
|
+
onboardingInstance.showLoadingModal();
|
|
489
|
+
}
|
|
490
|
+
}, 150);
|
|
384
491
|
}
|
|
385
492
|
onboardingRef.current = onboarding;
|
|
386
493
|
// Handle Google OAuth callback (only in useEffect, not exposed)
|
|
@@ -420,12 +527,17 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
420
527
|
}
|
|
421
528
|
catch (err) {
|
|
422
529
|
console.error('Google callback error:', err);
|
|
423
|
-
// Hide loading modal on error
|
|
530
|
+
// Hide loading modal on error (including whoami API failures)
|
|
424
531
|
if (onboardingRef.current) {
|
|
425
532
|
const onboardingAny = onboardingRef.current;
|
|
533
|
+
// Always try to hide loading modal first
|
|
426
534
|
if (onboardingAny.hideLoadingModal) {
|
|
427
535
|
onboardingAny.hideLoadingModal();
|
|
428
536
|
}
|
|
537
|
+
// Also hide any main modal overlay
|
|
538
|
+
if (onboardingAny.modalOverlay) {
|
|
539
|
+
onboardingAny.modalOverlay.style.display = 'none';
|
|
540
|
+
}
|
|
429
541
|
// Show error modal/screen
|
|
430
542
|
const errorMessage = err instanceof Error ? err.message : 'Google authentication failed';
|
|
431
543
|
if (onboardingAny.showError) {
|
|
@@ -434,6 +546,8 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
434
546
|
}
|
|
435
547
|
setError(err instanceof Error ? err : new Error('Google callback failed'));
|
|
436
548
|
googleCallbackHandledRef.current = false;
|
|
549
|
+
// Ensure body scroll is restored
|
|
550
|
+
document.body.style.overflow = '';
|
|
437
551
|
}
|
|
438
552
|
};
|
|
439
553
|
const handleDiscordCallback = async () => {
|
|
@@ -472,12 +586,17 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
472
586
|
}
|
|
473
587
|
catch (err) {
|
|
474
588
|
console.error('Discord callback error:', err);
|
|
475
|
-
// Hide loading modal on error
|
|
589
|
+
// Hide loading modal on error (including whoami API failures)
|
|
476
590
|
if (onboardingRef.current) {
|
|
477
591
|
const onboardingAny = onboardingRef.current;
|
|
592
|
+
// Always try to hide loading modal first
|
|
478
593
|
if (onboardingAny.hideLoadingModal) {
|
|
479
594
|
onboardingAny.hideLoadingModal();
|
|
480
595
|
}
|
|
596
|
+
// Also hide any main modal overlay
|
|
597
|
+
if (onboardingAny.modalOverlay) {
|
|
598
|
+
onboardingAny.modalOverlay.style.display = 'none';
|
|
599
|
+
}
|
|
481
600
|
// Show error modal/screen
|
|
482
601
|
const errorMessage = err instanceof Error ? err.message : 'Discord authentication failed';
|
|
483
602
|
if (onboardingAny.showError) {
|
|
@@ -486,6 +605,8 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
486
605
|
}
|
|
487
606
|
setError(err instanceof Error ? err : new Error('Discord callback failed'));
|
|
488
607
|
discordCallbackHandledRef.current = false;
|
|
608
|
+
// Ensure body scroll is restored
|
|
609
|
+
document.body.style.overflow = '';
|
|
489
610
|
}
|
|
490
611
|
};
|
|
491
612
|
const handleTwitterCallback = async () => {
|
|
@@ -524,12 +645,17 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
524
645
|
}
|
|
525
646
|
catch (err) {
|
|
526
647
|
console.error('Twitter callback error:', err);
|
|
527
|
-
// Hide loading modal on error
|
|
648
|
+
// Hide loading modal on error (including whoami API failures)
|
|
528
649
|
if (onboardingRef.current) {
|
|
529
650
|
const onboardingAny = onboardingRef.current;
|
|
651
|
+
// Always try to hide loading modal first
|
|
530
652
|
if (onboardingAny.hideLoadingModal) {
|
|
531
653
|
onboardingAny.hideLoadingModal();
|
|
532
654
|
}
|
|
655
|
+
// Also hide any main modal overlay
|
|
656
|
+
if (onboardingAny.modalOverlay) {
|
|
657
|
+
onboardingAny.modalOverlay.style.display = 'none';
|
|
658
|
+
}
|
|
533
659
|
// Show error modal/screen
|
|
534
660
|
const errorMessage = err instanceof Error ? err.message : 'Twitter authentication failed';
|
|
535
661
|
if (onboardingAny.showError) {
|
|
@@ -538,6 +664,8 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
538
664
|
}
|
|
539
665
|
setError(err instanceof Error ? err : new Error('Twitter callback failed'));
|
|
540
666
|
twitterCallbackHandledRef.current = false;
|
|
667
|
+
// Ensure body scroll is restored
|
|
668
|
+
document.body.style.overflow = '';
|
|
541
669
|
}
|
|
542
670
|
};
|
|
543
671
|
handleGoogleCallback();
|
|
@@ -1653,6 +1781,177 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
1653
1781
|
const currentChainId = isExternalWalletConnected && externalWalletChainId
|
|
1654
1782
|
? externalWalletChainId
|
|
1655
1783
|
: chainId;
|
|
1784
|
+
// Fetch balance for Abstraxn wallet (not external wallet)
|
|
1785
|
+
useEffect(() => {
|
|
1786
|
+
if (isExternalWalletConnected || !address || !currentChainId) {
|
|
1787
|
+
setWalletBalance(null);
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
const fetchBalance = async () => {
|
|
1791
|
+
try {
|
|
1792
|
+
const currentChain = getChainById(currentChainId);
|
|
1793
|
+
if (!currentChain || currentChain.type !== 'evm') {
|
|
1794
|
+
setWalletBalance(null);
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const publicClient = createPublicClient({
|
|
1798
|
+
chain: {
|
|
1799
|
+
id: currentChain.id,
|
|
1800
|
+
name: currentChain.name,
|
|
1801
|
+
nativeCurrency: currentChain.nativeCurrency,
|
|
1802
|
+
rpcUrls: {
|
|
1803
|
+
default: {
|
|
1804
|
+
http: [currentChain.rpcUrl],
|
|
1805
|
+
},
|
|
1806
|
+
},
|
|
1807
|
+
},
|
|
1808
|
+
transport: http(currentChain.rpcUrl),
|
|
1809
|
+
});
|
|
1810
|
+
const balance = await publicClient.getBalance({
|
|
1811
|
+
address: address,
|
|
1812
|
+
});
|
|
1813
|
+
setWalletBalance(balance);
|
|
1814
|
+
}
|
|
1815
|
+
catch (error) {
|
|
1816
|
+
console.error('Failed to fetch balance:', error);
|
|
1817
|
+
setWalletBalance(null);
|
|
1818
|
+
}
|
|
1819
|
+
};
|
|
1820
|
+
fetchBalance();
|
|
1821
|
+
// Refetch balance every 10 seconds
|
|
1822
|
+
const interval = setInterval(fetchBalance, 10000);
|
|
1823
|
+
return () => clearInterval(interval);
|
|
1824
|
+
}, [address, currentChainId, isExternalWalletConnected]);
|
|
1825
|
+
// Compute available chains from config - supports both legacy and new format
|
|
1826
|
+
const availableChains = useMemo(() => {
|
|
1827
|
+
const chains = [];
|
|
1828
|
+
const chainConfig = config.chains;
|
|
1829
|
+
// Priority 1: Check new chains config format
|
|
1830
|
+
if (chainConfig?.supportedEvmChains && chainConfig.supportedEvmChains.length > 0) {
|
|
1831
|
+
// Use configured chains from new format
|
|
1832
|
+
chainConfig.supportedEvmChains.forEach(chainName => {
|
|
1833
|
+
const chain = EVM_CHAINS[chainName];
|
|
1834
|
+
if (chain) {
|
|
1835
|
+
chains.push(chain);
|
|
1836
|
+
}
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
// Priority 2: Check legacy supportedChains format
|
|
1840
|
+
else if (config.supportedChains && config.supportedChains.length > 0) {
|
|
1841
|
+
// Convert legacy Chain format to ChainData
|
|
1842
|
+
config.supportedChains.forEach(legacyChain => {
|
|
1843
|
+
// Try to find matching chain by ID
|
|
1844
|
+
const chainData = getChainById(legacyChain.id);
|
|
1845
|
+
if (chainData) {
|
|
1846
|
+
chains.push(chainData);
|
|
1847
|
+
}
|
|
1848
|
+
else {
|
|
1849
|
+
// If not found, create ChainData from legacy chain
|
|
1850
|
+
const newChain = {
|
|
1851
|
+
id: legacyChain.id,
|
|
1852
|
+
name: legacyChain.name.toLowerCase().replace(/\s+/g, '-'),
|
|
1853
|
+
displayName: legacyChain.name,
|
|
1854
|
+
rpcUrl: legacyChain.rpcUrl,
|
|
1855
|
+
explorerUrl: `https://etherscan.io`, // Default explorer
|
|
1856
|
+
nativeCurrency: legacyChain.nativeCurrency,
|
|
1857
|
+
type: 'evm', // Assume EVM for legacy chains
|
|
1858
|
+
isTestnet: legacyChain.id !== 1 && legacyChain.id !== 137 && legacyChain.id !== 8453, // Common mainnets
|
|
1859
|
+
};
|
|
1860
|
+
chains.push(newChain);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
// Priority 3: Default chains only if nothing configured at all
|
|
1865
|
+
// Only show defaults if both config.chains and config.supportedChains are missing
|
|
1866
|
+
else if (!chainConfig && !config.supportedChains) {
|
|
1867
|
+
chains.push(EVM_CHAINS.ethereum, EVM_CHAINS.polygon, EVM_CHAINS.base);
|
|
1868
|
+
}
|
|
1869
|
+
// Add Solana chains if enabled (from new config format)
|
|
1870
|
+
if (chainConfig?.enableSolana) {
|
|
1871
|
+
chains.push(SOLANA_CHAINS.solana);
|
|
1872
|
+
}
|
|
1873
|
+
// Remove duplicates by chain ID
|
|
1874
|
+
const uniqueChains = chains.filter((chain, index, self) => index === self.findIndex(c => c.id === chain.id));
|
|
1875
|
+
// Only return defaults if absolutely nothing was configured
|
|
1876
|
+
return uniqueChains.length > 0
|
|
1877
|
+
? uniqueChains
|
|
1878
|
+
: (!chainConfig && !config.supportedChains
|
|
1879
|
+
? [EVM_CHAINS.ethereum, EVM_CHAINS.polygon, EVM_CHAINS.base]
|
|
1880
|
+
: []);
|
|
1881
|
+
}, [config.chains, config.supportedChains]);
|
|
1882
|
+
// Get current chain data
|
|
1883
|
+
const currentChain = useMemo(() => {
|
|
1884
|
+
if (!currentChainId)
|
|
1885
|
+
return null;
|
|
1886
|
+
return getChainById(currentChainId) || null;
|
|
1887
|
+
}, [currentChainId]);
|
|
1888
|
+
// Enhanced switchChain that works for both EVM and Solana
|
|
1889
|
+
const switchChainEnhanced = useCallback(async (targetChainId) => {
|
|
1890
|
+
if (!walletRef.current) {
|
|
1891
|
+
throw new Error('Wallet not initialized');
|
|
1892
|
+
}
|
|
1893
|
+
// Check if chain is in availableChains (user-configured chains)
|
|
1894
|
+
const targetChainInAvailable = availableChains?.find(c => c.id === targetChainId);
|
|
1895
|
+
if (!targetChainInAvailable) {
|
|
1896
|
+
throw new Error(`Chain with ID ${targetChainId} is not available. Please configure it in your SDK setup.`);
|
|
1897
|
+
}
|
|
1898
|
+
const targetChain = getChainById(targetChainId);
|
|
1899
|
+
if (!targetChain) {
|
|
1900
|
+
throw new Error(`Chain with ID ${targetChainId} is not supported`);
|
|
1901
|
+
}
|
|
1902
|
+
setLoading(true);
|
|
1903
|
+
setError(null);
|
|
1904
|
+
try {
|
|
1905
|
+
if (targetChain.type === 'evm') {
|
|
1906
|
+
// For EVM chains, use the existing switchChain
|
|
1907
|
+
if (isExternalWalletConnected && wagmiSwitchChain) {
|
|
1908
|
+
// External wallet - use wagmi switchChain
|
|
1909
|
+
await wagmiSwitchChain.switchChainAsync({ chainId: targetChainId });
|
|
1910
|
+
setExternalWalletChainId(targetChainId);
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
// Abstraxn wallet - try to switch, but if chain is not in wallet's supportedChains,
|
|
1914
|
+
// we'll update the state directly (for chains that are in availableChains but not in wallet config)
|
|
1915
|
+
try {
|
|
1916
|
+
await switchChain(targetChainId);
|
|
1917
|
+
}
|
|
1918
|
+
catch (switchErr) {
|
|
1919
|
+
// If the wallet doesn't support this chain, but it's in availableChains,
|
|
1920
|
+
// we can still update the state to allow UI to work
|
|
1921
|
+
if (switchErr?.message?.includes('not supported') && targetChainInAvailable) {
|
|
1922
|
+
// Update chainId state directly for chains in availableChains
|
|
1923
|
+
setChainId(targetChainId);
|
|
1924
|
+
// Emit chainChanged event if wallet supports it
|
|
1925
|
+
if (walletRef.current && typeof walletRef.current.emit === 'function') {
|
|
1926
|
+
walletRef.current.emit('chainChanged', targetChainId);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
else {
|
|
1930
|
+
throw switchErr;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
else if (targetChain.type === 'solana') {
|
|
1936
|
+
// For Solana, we might need different handling
|
|
1937
|
+
// For now, just update the chainId state
|
|
1938
|
+
// TODO: Implement actual Solana chain switching when wallet supports it
|
|
1939
|
+
setChainId(targetChainId);
|
|
1940
|
+
}
|
|
1941
|
+
// Update chainId state
|
|
1942
|
+
if (!isExternalWalletConnected) {
|
|
1943
|
+
setChainId(targetChainId);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
catch (err) {
|
|
1947
|
+
const error = err instanceof Error ? err : new Error(`Failed to switch to chain ${targetChainId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1948
|
+
setError(error);
|
|
1949
|
+
throw error;
|
|
1950
|
+
}
|
|
1951
|
+
finally {
|
|
1952
|
+
setLoading(false);
|
|
1953
|
+
}
|
|
1954
|
+
}, [isExternalWalletConnected, wagmiSwitchChain, switchChain, availableChains]);
|
|
1656
1955
|
const value = {
|
|
1657
1956
|
wallet: walletRef.current,
|
|
1658
1957
|
isInitialized,
|
|
@@ -1692,6 +1991,12 @@ function AbstraxnProviderInner({ config, children, base, wagmi }) {
|
|
|
1692
1991
|
externalWalletNetwork: externalWalletsEnabled && wagmiChainIdHook ? getNetworkName(wagmiChainIdHook) : undefined,
|
|
1693
1992
|
availableConnectors: externalWalletsEnabled ? availableConnectors : undefined,
|
|
1694
1993
|
switchExternalWalletChain: externalWalletsEnabled ? switchExternalWalletChain : undefined,
|
|
1994
|
+
// Chain management
|
|
1995
|
+
switchChainEnhanced,
|
|
1996
|
+
currentChain,
|
|
1997
|
+
availableChains,
|
|
1998
|
+
// Balance (for Abstraxn wallet)
|
|
1999
|
+
walletBalance: !isExternalWalletConnected ? walletBalance : undefined,
|
|
1695
2000
|
};
|
|
1696
2001
|
// Ref to store latest value to avoid dependency issues (defined after value)
|
|
1697
2002
|
const valueRef = useRef(value);
|
|
@@ -2168,11 +2473,35 @@ export function AbstraxnProvider({ config, children }) {
|
|
|
2168
2473
|
// Create wagmi config if external wallets are enabled
|
|
2169
2474
|
// Use useMemo to ensure config is created synchronously during render
|
|
2170
2475
|
// This prevents the "Loading wallet connectors..." state from flashing or blocking initial render
|
|
2476
|
+
// Compute chains for wagmi config (needs to be done before useMemo)
|
|
2477
|
+
const wagmiChains = useMemo(() => {
|
|
2478
|
+
// Use the same logic as availableChains but convert to Core Chain format
|
|
2479
|
+
const chains = [];
|
|
2480
|
+
const chainConfig = config.chains;
|
|
2481
|
+
// Priority 1: Check new chains config format
|
|
2482
|
+
if (chainConfig?.supportedEvmChains && chainConfig.supportedEvmChains.length > 0) {
|
|
2483
|
+
chainConfig.supportedEvmChains.forEach(chainName => {
|
|
2484
|
+
const chainData = EVM_CHAINS[chainName];
|
|
2485
|
+
if (chainData && chainData.type === 'evm') {
|
|
2486
|
+
chains.push(toCoreChain(chainData));
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
// Priority 2: Check legacy supportedChains format
|
|
2491
|
+
else if (config.supportedChains && config.supportedChains.length > 0) {
|
|
2492
|
+
chains.push(...config.supportedChains);
|
|
2493
|
+
}
|
|
2494
|
+
// Priority 3: Default chains
|
|
2495
|
+
else {
|
|
2496
|
+
chains.push(toCoreChain(EVM_CHAINS.ethereum));
|
|
2497
|
+
}
|
|
2498
|
+
return chains.length > 0 ? chains : [toCoreChain(EVM_CHAINS.ethereum)];
|
|
2499
|
+
}, [config.chains, config.supportedChains]);
|
|
2171
2500
|
const { wagmiConfig, configError } = useMemo(() => {
|
|
2172
2501
|
if (!externalWalletsEnabled)
|
|
2173
2502
|
return { wagmiConfig: null, configError: null };
|
|
2174
2503
|
try {
|
|
2175
|
-
const configResult = createWagmiConfig(
|
|
2504
|
+
const configResult = createWagmiConfig(wagmiChains, config.externalWallets?.walletConnectProjectId, config.externalWallets?.connectors, config.ui?.theme || 'dark');
|
|
2176
2505
|
return { wagmiConfig: configResult, configError: null };
|
|
2177
2506
|
}
|
|
2178
2507
|
catch (error) {
|
|
@@ -2182,7 +2511,7 @@ export function AbstraxnProvider({ config, children }) {
|
|
|
2182
2511
|
configError: error instanceof Error ? error : new Error('Failed to create wagmi config')
|
|
2183
2512
|
};
|
|
2184
2513
|
}
|
|
2185
|
-
}, [externalWalletsEnabled,
|
|
2514
|
+
}, [externalWalletsEnabled, wagmiChains, config.externalWallets?.walletConnectProjectId, config.externalWallets?.connectors]);
|
|
2186
2515
|
// If external wallets are enabled, wrap with QueryClientProvider and WagmiProvider
|
|
2187
2516
|
if (externalWalletsEnabled) {
|
|
2188
2517
|
if (configError) {
|