@0xsequence/connect
Version:
Connect package for Sequence Web SDK
224 lines • 8.79 kB
JavaScript
'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