UNPKG

@0xsequence/connect

Version:
224 lines 8.79 kB
'use client'; import { useAPIClient } from '@0xsequence/hooks'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useAccount, useConnect, useConnections, useDisconnect } from 'wagmi'; import { SEQUENCE_UNIVERSAL_CONNECTOR_NAME } from '../components/Connect/Connect.js'; import { useWaasGetLinkedWalletsSignature } from './useWaasGetLinkedWalletsSignature.js'; // Create a stable storage key from args const createStorageKey = (args) => `@0xsequence.linked_wallets-${args.parentWalletAddress}-${args.signatureChainId}`; const getLinkedWallets = async (apiClient, args, headers, signal) => { const storageKey = createStorageKey(args); const now = Date.now(); // Check localStorage for cached data const stored = localStorage.getItem(storageKey); if (stored) { try { const { data, timestamp } = JSON.parse(stored); // Check if cache is still valid (5 minutes) if (now - timestamp <= 5 * 60 * 1000) { return data; } } catch (error) { console.error('Error parsing stored linked wallets:', error); } } // If no valid cache, fetch new data const result = await apiClient.getLinkedWallets(args, headers, signal); const linkedWallets = result.linkedWallets; // Store in localStorage with timestamp localStorage.setItem(storageKey, JSON.stringify({ data: linkedWallets, timestamp: now })); return linkedWallets; }; // --- Listener pattern for cross-instance updates --- const linkedWalletsListeners = new Set(); const notifyLinkedWalletsListeners = () => { // Use setTimeout to ensure notification happens after the current execution context, // allowing React state updates to potentially settle before listeners run. setTimeout(() => { linkedWalletsListeners.forEach(listener => listener()); }, 0); }; export const useLinkedWallets = (args, options = {}) => { const apiClient = useAPIClient(); const [data, setData] = useState(undefined); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const abortControllerRef = useRef(undefined); const fetchData = useCallback(async () => { if (!options.enabled) { return; } setIsLoading(true); setError(null); try { // Cancel any ongoing request abortControllerRef.current?.abort(); abortControllerRef.current = new AbortController(); // @ts-ignore const linkedWallets = await getLinkedWallets(apiClient, args, undefined, abortControllerRef.current.signal); setData(linkedWallets); } catch (error) { if (error instanceof Error && error.name !== 'AbortError') { setError(error); } else if (error && typeof error === 'object' && 'name' in error && error.name !== 'AbortError') { setError(new Error('Failed to fetch linked wallets')); } } finally { setIsLoading(false); } }, [apiClient, args.parentWalletAddress, args.signatureChainId, options.enabled]); // Fetch on mount, when dependencies change, and register/unregister listener useEffect(() => { // Register the listener linkedWalletsListeners.add(fetchData); // Initial fetch fetchData(); // Cleanup: remove listener and abort ongoing request return () => { linkedWalletsListeners.delete(fetchData); abortControllerRef.current?.abort(); }; }, [fetchData]); const clearCache = useCallback(() => { localStorage.removeItem(createStorageKey(args)); }, [args]); const refetch = async () => { clearCache(); await fetchData(); // Notify other hook instances after successful fetch notifyLinkedWalletsListeners(); }; return { data, isLoading, error, refetch, clearCache }; }; /** * Hook to manage connected wallets. * * This hook provides information about all connected wallets, including both * embedded wallets (WaaS) and external wallets. It also allows managing these * connections by setting active wallets or disconnecting them. * * For embedded wallets, it also provides access to linked wallets - additional * wallets that have been linked to the primary embedded wallet. * * @see {@link https://docs.sequence.xyz/sdk/web/wallet-sdk/ecosystem/hooks/useWallets} for more detailed documentation. * * @returns An object containing wallet information and management functions {@link UseWalletsReturnType} * * @example * ```tsx * import { useWallets } from '@0xsequence/connect' * * const YourComponent = () => { * const { wallets, setActiveWallet, disconnectWallet } = useWallets() * * return ( * <div> * <h2>Connected Wallets</h2> * <div> * {wallets.map(wallet => ( * <div key={wallet.address}> * <span>{wallet.name}: {wallet.address}</span> * {wallet.isActive ? ' (Active)' : ''} * {wallet.isEmbedded ? ' (Embedded)' : ''} * <button onClick={() => setActiveWallet(wallet.address)}> * Set Active * </button> * <button onClick={() => disconnectWallet(wallet.address)}> * Disconnect * </button> * </div> * ))} * </div> * </div> * ) * } * ``` */ export const useWallets = () => { const { address } = useAccount(); const connections = useConnections(); const { connectAsync } = useConnect(); const { disconnectAsync } = useDisconnect(); const waasConnection = connections.find(c => c.connector?.type === 'sequence-waas'); const { message: linkedWalletsMessage, signature: linkedWalletsSignature, address: linkedWalletsWaasAddress, chainId: linkedWalletsSigChainId } = useWaasGetLinkedWalletsSignature(waasConnection); // Only fetch if we have valid data const hasValidData = !!(linkedWalletsWaasAddress && linkedWalletsMessage && linkedWalletsSignature); const { data: linkedWallets, refetch: refetchLinkedWallets, clearCache: clearLinkedWalletsCache } = useLinkedWallets({ parentWalletAddress: linkedWalletsWaasAddress || '', parentWalletMessage: linkedWalletsMessage || '', parentWalletSignature: linkedWalletsSignature || '', signatureChainId: `${linkedWalletsSigChainId}` }, { enabled: hasValidData }); const wallets = connections.map((connection) => ({ id: connection.connector.id, name: getConnectorName(connection.connector), address: connection.accounts[0], isActive: connection.accounts[0] === address, isEmbedded: connection.connector.id.includes('waas'), signInMethod: connection.connector._wallet?.id })); const setActiveWallet = async (walletAddress) => { const connection = connections.find((c) => c.accounts[0].toLowerCase() === walletAddress.toLowerCase()); if (!connection) { console.error('No connection found for wallet address:', walletAddress); return; } // Do not try to change if it's already active if (wallets.find(w => w.address.toLowerCase() === walletAddress.toLowerCase())?.isActive) { return; } try { await connectAsync({ connector: connection.connector }); } catch (error) { console.error('Failed to set active wallet:', error); } }; const disconnectWallet = async (walletAddress) => { const connection = connections.find((c) => c.accounts[0].toLowerCase() === walletAddress.toLowerCase()); if (!connection) { return; } // invalidate linked wallets if we're disconnecting waas wallet if (connection.connector.id.includes('waas')) { clearLinkedWalletsCache(); } try { await disconnectAsync({ connector: connection.connector }); } catch (error) { console.error('Failed to disconnect wallet:', error); } }; return { wallets, linkedWallets, setActiveWallet, disconnectWallet, refetchLinkedWallets }; }; const getConnectorName = (connector) => { const connectorName = connector.name; const connectorWalletName = connector._wallet?.name; if (connectorName === SEQUENCE_UNIVERSAL_CONNECTOR_NAME) { return 'Sequence Universal'; } return connectorWalletName ?? connectorName; }; //# sourceMappingURL=useWallets.js.map