Skip to content

Latest commit

 

History

History
328 lines (245 loc) · 7.78 KB

File metadata and controls

328 lines (245 loc) · 7.78 KB

Wallet Connectors - Lazy Loaded Modules

This directory contains lazy-loaded wallet connector implementations. These modules are dynamically imported only when a user initiates wallet connection, reducing initial bundle size.

Structure

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

Architecture

Why Lazy Loading?

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

How It Works

  1. Initial Page Load

    • Wallet connectors are NOT imported
    • Main bundle is lighter
    • Page loads faster
  2. 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
  3. Subsequent Uses

    • Module is cached
    • Load from memory
    • Instant connection

Usage

From Components

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>
  );
}

Direct Import (Advanced)

// Only use this if you specifically need individual connectors
const { connectMetaMaskWallet } = await import('@/lib/walletConnectors/metamask');
const result = await connectMetaMaskWallet();

Module Details

metamask.ts

Exports:

  • connectMetaMaskWallet() - Connects to MetaMask
  • isMetaMaskAvailable() - Checks if MetaMask is installed

Size: ~45 KB Dependencies: None (uses window.ethereum) Load Time: ~100-150ms

coinbase.ts

Exports:

  • connectCoinbaseWallet() - Connects to Coinbase Wallet
  • isCoinbaseAvailable() - Checks if Coinbase is installed

Size: ~38 KB
Dependencies: None (uses window.ethereum) Load Time: ~100-120ms

walletconnect.ts

Exports:

  • connectWalletConnectWallet() - Connects via WalletConnect
  • isWalletConnectConfigured() - Checks if configured

Size: ~95 KB (largest) Dependencies: @walletconnect/web3-provider Load Time: ~200-300ms (first load)

Error Handling

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.

Return Type

All connectors return:

interface ConnectorResult {
  address: string;      // Connected wallet address (0x...)
  chainId: number;      // Chain ID (1 for Ethereum, 137 for Polygon, etc.)
}

Adding New Wallets

To add a new wallet connector:

  1. Create ./newwallet.ts
  2. Export connectNewWalletWallet() function
  3. Return ConnectorResult
  4. Update useWalletConnector.ts to include new wallet
  5. Update WalletModal.tsx to 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
};

Performance Monitoring

Check Bundle Size

npm run bundle:measure

Monitor Load Times

performance.mark('wallet-init-start');
const result = await connectWallet('metamask');
performance.mark('wallet-init-end');
performance.measure('wallet-init', 'wallet-init-start', 'wallet-init-end');

Network Inspection

  1. Open DevTools → Network tab
  2. Click "Connect Wallet"
  3. Look for chunks being downloaded:
    • metamask.chunk.js
    • coinbase.chunk.js
    • walletconnect.chunk.js

Testing

Unit Test Example

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();
  });
});

Best Practices

  1. Always use the hook

    // ✅ Good
    const { connectWallet } = useWalletConnector();
    
    // ❌ Bad - breaks lazy loading
    import { connectMetaMaskWallet } from '@/lib/walletConnectors/metamask';
  2. Show loading states

    // ✅ Good
    <button disabled={isLoadingConnector}>
      {isLoadingConnector ? 'Loading...' : 'Connect'}
    </button>
    
    // ❌ Bad - poor UX
    <button>Connect</button>
  3. Handle errors gracefully

    // ✅ Good
    try {
      await connectWallet('metamask');
    } catch (error) {
      setError(getWalletErrorMessage(error));
    }
    
    // ❌ Bad - unhandled
    await connectWallet('metamask');
  4. 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

Troubleshooting

Connector Not Loading

Symptom: Click button, no loading indicator, connection fails

Debug:

  1. Check DevTools Network tab for chunk download
  2. Check Console for errors
  3. Verify dynamic import syntax is correct

Slow Loading

Symptom: Long delay (>500ms) before loading indicator appears

Check:

  1. Network tab - is chunk being downloaded?
  2. Network throttling - are you on fast connection?
  3. Browser cache - is chunk cached?

Module Not Found Errors

Symptom: Cannot find module '@/lib/walletConnectors/...'

Fix:

  1. Verify file exists and is named correctly
  2. Check TypeScript paths in tsconfig.json
  3. Rebuild project: npm run build

References