signer-test-sdk-react 0.0.10 → 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.
Files changed (30) hide show
  1. package/README.md +1 -1
  2. package/dist/src/AbstraxnProvider.js +361 -32
  3. package/dist/src/AbstraxnProvider.js.map +1 -1
  4. package/dist/src/WalletModal.css +1438 -14
  5. package/dist/src/WalletModal.d.ts +2 -1
  6. package/dist/src/WalletModal.js +249 -13
  7. package/dist/src/WalletModal.js.map +1 -1
  8. package/dist/src/chains.d.ts +55 -0
  9. package/dist/src/chains.js +221 -0
  10. package/dist/src/chains.js.map +1 -0
  11. package/dist/src/components/OnboardingUI/OnboardingUIReact.js +49 -9
  12. package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -1
  13. package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +4 -2
  14. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +97 -16
  15. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -1
  16. package/dist/src/components/OnboardingUI/components/OtpForm.js +3 -3
  17. package/dist/src/components/OnboardingUI/components/OtpForm.js.map +1 -1
  18. package/dist/src/components/OnboardingUI/components/PasskeyButton.js +1 -1
  19. package/dist/src/components/OnboardingUI/components/PasskeyButton.js.map +1 -1
  20. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +2 -0
  21. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -1
  22. package/dist/src/components/QRCode.d.ts +13 -0
  23. package/dist/src/components/QRCode.js +6 -0
  24. package/dist/src/components/QRCode.js.map +1 -0
  25. package/dist/src/index.d.ts +2 -1
  26. package/dist/src/index.js +1 -0
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/types.d.ts +29 -0
  29. package/dist/tsconfig.tsbuildinfo +1 -1
  30. 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: config.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
- return { success: false };
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 && onboardingAny.modalOverlay) {
379
- // If success=true is in URL, ensure modal is open to show loading
380
- onboardingAny.modalOverlay.classList.remove('onboarding-modal-closing');
381
- onboardingAny.modalOverlay.classList.add('onboarding-modal-open');
382
- onboardingAny.modalOverlay.style.display = 'flex';
383
- document.body.style.overflow = 'hidden';
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(config.supportedChains, config.externalWallets?.walletConnectProjectId, config.externalWallets?.connectors, config.ui?.theme || 'dark');
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, config.supportedChains, config.externalWallets?.walletConnectProjectId, config.externalWallets?.connectors]);
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) {