This directory contains lazy-loaded wallet connector implementations. These modules are dynamically imported only when a user initiates wallet connection, reducing initial bundle size.
walletConnectors/
├── metamask.ts # MetaMask wallet connector (~45 KB when loaded)
├── coinbase.ts # Coinbase wallet connector (~38 KB when loaded)
├── walletconnect.ts # WalletConnect connector (~95 KB when loaded)
└── README.md # This file
Wallet SDK libraries are large (~178 KB combined) and not needed on initial page load. By dynamically importing them only when needed, we:
- ✅ Reduce initial bundle by ~178 KB (91.6% reduction)
- ✅ Improve time to interactive (TTI) by ~600ms
- ✅ Maintain same functionality
- ✅ Enable better caching strategies
-
Initial Page Load
- Wallet connectors are NOT imported
- Main bundle is lighter
- Page loads faster
-
User Clicks "Connect Wallet"
- Hook calls
await import('@/lib/walletConnectors/metamask') - Browser downloads
metamask.chunk.js(~45 KB) - User sees loading indicator (150-200ms)
- Connection proceeds
- Hook calls
-
Subsequent Uses
- Module is cached
- Load from memory
- Instant connection
import { useWalletConnector } from '@/hooks/useWalletConnector';
export function WalletButton() {
const { connectWallet, isLoadingConnector } = useWalletConnector();
const handleClick = async () => {
try {
const result = await connectWallet('metamask');
console.log('Connected:', result.address);
} catch (error) {
console.error('Failed:', error);
}
};
return (
<button onClick={handleClick} disabled={isLoadingConnector}>
{isLoadingConnector ? 'Loading...' : 'Connect MetaMask'}
</button>
);
}// Only use this if you specifically need individual connectors
const { connectMetaMaskWallet } = await import('@/lib/walletConnectors/metamask');
const result = await connectMetaMaskWallet();Exports:
connectMetaMaskWallet()- Connects to MetaMaskisMetaMaskAvailable()- Checks if MetaMask is installed
Size: ~45 KB
Dependencies: None (uses window.ethereum)
Load Time: ~100-150ms
Exports:
connectCoinbaseWallet()- Connects to Coinbase WalletisCoinbaseAvailable()- Checks if Coinbase is installed
Size: ~38 KB
Dependencies: None (uses window.ethereum)
Load Time: ~100-120ms
Exports:
connectWalletConnectWallet()- Connects via WalletConnectisWalletConnectConfigured()- Checks if configured
Size: ~95 KB (largest)
Dependencies: @walletconnect/web3-provider
Load Time: ~200-300ms (first load)
Each connector handles specific errors:
// User rejected connection
{
code: 4001,
message: "User rejected the connection request"
}
// Already pending request
{
code: -32002,
message: "Request is already pending"
}
// Installation missing
{
message: "MetaMask is not installed"
}All errors are wrapped with user-friendly messages by the hook.
All connectors return:
interface ConnectorResult {
address: string; // Connected wallet address (0x...)
chainId: number; // Chain ID (1 for Ethereum, 137 for Polygon, etc.)
}To add a new wallet connector:
- Create
./newwallet.ts - Export
connectNewWalletWallet()function - Return
ConnectorResult - Update
useWalletConnector.tsto include new wallet - Update
WalletModal.tsxto show new option
Template:
// src/lib/walletConnectors/newwallet.ts
export interface NewWalletConnectorResult {
address: string;
chainId: number;
}
export const connectNewWalletWallet = async (): Promise<NewWalletConnectorResult> => {
try {
// Check if wallet is available
// Request connection
// Get address and chain ID
// Return result
} catch (error) {
// Handle errors
throw error;
}
};
export const isNewWalletAvailable = (): boolean => {
// Check availability
};npm run bundle:measureperformance.mark('wallet-init-start');
const result = await connectWallet('metamask');
performance.mark('wallet-init-end');
performance.measure('wallet-init', 'wallet-init-start', 'wallet-init-end');- Open DevTools → Network tab
- Click "Connect Wallet"
- Look for chunks being downloaded:
metamask.chunk.jscoinbase.chunk.jswalletconnect.chunk.js
import { connectMetaMaskWallet } from '@/lib/walletConnectors/metamask';
describe('MetaMask Connector', () => {
it('should connect successfully', async () => {
// Mock window.ethereum
window.ethereum = {
isMetaMask: true,
request: jest.fn()
.mockResolvedValueOnce(['0x742d35Cc6634C0532925a3b8D4C9db96C4b4Db45']) // accounts
.mockResolvedValueOnce('0x1'), // chainId
};
const result = await connectMetaMaskWallet();
expect(result.address).toBe('0x742d35Cc6634C0532925a3b8D4C9db96C4b4Db45');
expect(result.chainId).toBe(1);
});
it('should handle user rejection', async () => {
const error = new Error('User rejected');
(error as any).code = 4001;
window.ethereum = {
isMetaMask: true,
request: jest.fn().rejects(error),
};
await expect(connectMetaMaskWallet()).rejects.toThrow();
});
});-
Always use the hook
// ✅ Good const { connectWallet } = useWalletConnector(); // ❌ Bad - breaks lazy loading import { connectMetaMaskWallet } from '@/lib/walletConnectors/metamask';
-
Show loading states
// ✅ Good <button disabled={isLoadingConnector}> {isLoadingConnector ? 'Loading...' : 'Connect'} </button> // ❌ Bad - poor UX <button>Connect</button>
-
Handle errors gracefully
// ✅ Good try { await connectWallet('metamask'); } catch (error) { setError(getWalletErrorMessage(error)); } // ❌ Bad - unhandled await connectWallet('metamask');
-
Validate before using
// ✅ Good const { address, chainId } = result; if (!address || chainId < 0) throw new Error('Invalid result'); // ❌ Bad - unsafe const { address, chainId } = result; // Assume valid
Symptom: Click button, no loading indicator, connection fails
Debug:
- Check DevTools Network tab for chunk download
- Check Console for errors
- Verify dynamic import syntax is correct
Symptom: Long delay (>500ms) before loading indicator appears
Check:
- Network tab - is chunk being downloaded?
- Network throttling - are you on fast connection?
- Browser cache - is chunk cached?
Symptom: Cannot find module '@/lib/walletConnectors/...'
Fix:
- Verify file exists and is named correctly
- Check TypeScript paths in
tsconfig.json - Rebuild project:
npm run build