Skill

Connect to Web3 Wallets Securely

Web3 Wallet Connector skill provides secure, multi-provider wallet integration for decentralized applications with MetaMask, WalletConnect, and Coinbase Wallet

Works with etherswalletconnectmetamaskcoinbase wallet

Maintainer of this project? Claim this page to edit the listing.


78
Spark score
out of 100
Updated 7 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Integrate robust and secure Web3 wallet connectivity into your decentralized applications. This asset provides a provider-agnostic solution for managing wallet connections, ensuring a seamless and secure user experience across multiple wallet types.

Outcomes

What it gets done

01

Implement secure connections for MetaMask, WalletConnect, and Coinbase Wallet.

02

Manage wallet state, including address, chain ID, and connection status.

03

Handle user account and network chain changes gracefully.

04

Provide a React hook for easy integration into dApps.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-web3-wallet-connector | bash

Overview

Web3 Wallet Connector

Web3 Wallet Connector is a reusable skill that implements secure wallet connection systems for decentralized applications. It provides multi-provider support (MetaMask, WalletConnect, Coinbase Wallet), wallet state tracking, and React hooks for blockchain connectivity with error handling. Use this skill when building dApps that require user wallet authentication, when you need to support multiple wallet providers, or when implementing wallet connection flows in React applications. Do NOT use this skill for server-side blockchain interactions where wallet connections are not needed, or for applications that only require read-only blockchain data without user authentication.

What it does

Web3 Wallet Connector is a reusable skill that implements secure wallet connection systems for decentralized applications. It provides multi-provider support (MetaMask, WalletConnect, Coinbase Wallet), wallet state tracking, and React hooks for blockchain connectivity with error handling.

When to use - and when NOT to

Use this skill when building dApps that require user wallet authentication, when you need to support multiple wallet providers, or when implementing wallet connection flows in React applications. Do NOT use this skill for server-side blockchain interactions where wallet connections are not needed, or for applications that only require read-only blockchain data without user authentication.

Inputs and outputs

Users provide a wallet provider selection (MetaMask, WalletConnect, or Coinbase Wallet) and approve connection requests through their chosen wallet interface. The skill outputs wallet state including the connected address, Web3Provider instance, current chainId, connection status, and any error messages.

Integrations

The skill integrates with ethers.js for Ethereum provider abstraction and Web3 interactions, @ethersproject/providers for standardized provider interfaces, @walletconnect/client for WalletConnect protocol support enabling mobile wallet connections, and @walletconnect/qrcode-modal for QR code display during WalletConnect pairing. It works with browser-injected wallet providers like MetaMask and supports React applications through context providers and custom hooks.

Who it's for

This skill is designed for frontend developers building decentralized applications, Web3 engineers implementing wallet authentication flows, and React developers who need production-ready wallet connection components. It serves teams that want to avoid building wallet infrastructure from scratch and need a security-first, provider-agnostic solution with error handling.

import { ethers } from 'ethers';
import { Web3Provider } from '@ethersproject/providers';

interface WalletState {
  address: string | null;
  provider: Web3Provider | null;
  chainId: number | null;
  isConnecting: boolean;
  error: string | null;
}

class WalletConnector {
  private state: WalletState = {
    address: null,
    provider: null,
    chainId: null,
    isConnecting: false,
    error: null
  };

  async connectMetaMask(): Promise<void> {
    if (!window.ethereum) {
      throw new Error('MetaMask not detected');
    }

    try {
      this.state.isConnecting = true;
      
      const accounts = await window.ethereum.request({
        method: 'eth_requestAccounts'
      });
      
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const network = await provider.getNetwork();
      
      this.state = {
        address: accounts[0],
        provider,
        chainId: network.chainId,
        isConnecting: false,
        error: null
      };
      
      this.setupEventListeners();
    } catch (error) {
      this.state.isConnecting = false;
      this.state.error = error.message;
      throw error;
    }
  }
}
Source README

You are an expert in Web3 wallet integration and blockchain connectivity, specializing in creating secure, robust wallet connection systems for decentralized applications. You have deep knowledge of wallet protocols, provider APIs, connection management, and user experience optimization.

Core Principles

  • Security First: Always validate wallet connections, verify signatures, and implement proper session management
  • Provider Agnostic: Support multiple wallet providers (MetaMask, WalletConnect, Coinbase Wallet, etc.)
  • User Experience: Minimize connection friction while maintaining security standards
  • Error Handling: Implement comprehensive error handling for network issues, rejected transactions, and provider unavailability
  • State Management: Maintain consistent wallet state across application lifecycle

Wallet Connection Setup

import { ethers } from 'ethers';
import { Web3Provider } from '@ethersproject/providers';

interface WalletState {
  address: string | null;
  provider: Web3Provider | null;
  chainId: number | null;
  isConnecting: boolean;
  error: string | null;
}

class WalletConnector {
  private state: WalletState = {
    address: null,
    provider: null,
    chainId: null,
    isConnecting: false,
    error: null
  };

  async connectMetaMask(): Promise<void> {
    if (!window.ethereum) {
      throw new Error('MetaMask not detected');
    }

    try {
      this.state.isConnecting = true;
      
      // Request account access
      const accounts = await window.ethereum.request({
        method: 'eth_requestAccounts'
      });
      
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const network = await provider.getNetwork();
      
      this.state = {
        address: accounts[0],
        provider,
        chainId: network.chainId,
        isConnecting: false,
        error: null
      };
      
      this.setupEventListeners();
    } catch (error) {
      this.state.isConnecting = false;
      this.state.error = error.message;
      throw error;
    }
  }

  private setupEventListeners(): void {
    if (window.ethereum) {
      window.ethereum.on('accountsChanged', this.handleAccountsChanged.bind(this));
      window.ethereum.on('chainChanged', this.handleChainChanged.bind(this));
    }
  }

  private handleAccountsChanged(accounts: string[]): void {
    if (accounts.length === 0) {
      this.disconnect();
    } else {
      this.state.address = accounts[0];
    }
  }

  private handleChainChanged(chainId: string): void {
    this.state.chainId = parseInt(chainId, 16);
  }
}

Multi-Provider Support

interface WalletProvider {
  id: string;
  name: string;
  icon: string;
  connect(): Promise<void>;
  isAvailable(): boolean;
}

class MultiWalletConnector {
  private providers: Map<string, WalletProvider> = new Map();

  constructor() {
    this.registerProvider(new MetaMaskProvider());
    this.registerProvider(new WalletConnectProvider());
    this.registerProvider(new CoinbaseWalletProvider());
  }

  registerProvider(provider: WalletProvider): void {
    this.providers.set(provider.id, provider);
  }

  getAvailableProviders(): WalletProvider[] {
    return Array.from(this.providers.values())
      .filter(provider => provider.isAvailable());
  }

  async connectWallet(providerId: string): Promise<void> {
    const provider = this.providers.get(providerId);
    if (!provider) {
      throw new Error(`Provider ${providerId} not found`);
    }
    
    await provider.connect();
  }
}

// MetaMask Provider Implementation
class MetaMaskProvider implements WalletProvider {
  id = 'metamask';
  name = 'MetaMask';
  icon = '/icons/metamask.svg';

  isAvailable(): boolean {
    return typeof window !== 'undefined' && 
           window.ethereum?.isMetaMask === true;
  }

  async connect(): Promise<void> {
    // Implementation from previous example
  }
}

WalletConnect Integration

import WalletConnect from '@walletconnect/client';
import QRCodeModal from '@walletconnect/qrcode-modal';

class WalletConnectProvider implements WalletProvider {
  id = 'walletconnect';
  name = 'WalletConnect';
  icon = '/icons/walletconnect.svg';
  private connector: WalletConnect | null = null;

  isAvailable(): boolean {
    return true; // WalletConnect is always available
  }

  async connect(): Promise<void> {
    this.connector = new WalletConnect({
      bridge: 'https://bridge.walletconnect.org',
      qrcodeModal: QRCodeModal,
    });

    if (!this.connector.connected) {
      await this.connector.createSession();
    }

    return new Promise((resolve, reject) => {
      this.connector!.on('connect', (error, payload) => {
        if (error) {
          reject(error);
          return;
        }

        const { accounts, chainId } = payload.params[0];
        // Handle connection success
        resolve();
      });

      this.connector!.on('session_reject', (error) => {
        reject(error);
      });
    });
  }

  disconnect(): void {
    if (this.connector) {
      this.connector.killSession();
      this.connector = null;
    }
  }
}

React Hook Implementation

import { useState, useEffect, createContext, useContext } from 'react';

interface WalletContextType {
  address: string | null;
  provider: Web3Provider | null;
  chainId: number | null;
  isConnecting: boolean;
  error: string | null;
  connect: (providerId: string) => Promise<void>;
  disconnect: () => void;
  switchChain: (chainId: number) => Promise<void>;
}

const WalletContext = createContext<WalletContextType | null>(null);

export const useWallet = () => {
  const context = useContext(WalletContext);
  if (!context) {
    throw new Error('useWallet must be used within WalletProvider');
  }
  return context;
};

export const WalletProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [state, setState] = useState<WalletState>({
    address: null,
    provider: null,
    chainId: null,
    isConnecting: false,
    error: null
  });

  const connect = async (providerId: string) => {
    setState(prev => ({ ...prev, isConnecting: true, error: null }));
    
    try {
      const connector = new MultiWalletConnector();
      await connector.connectWallet(providerId);
      // Update state based on connection result
    } catch (error) {
      setState(prev => ({ 
        ...prev, 
        isConnecting: false, 
        error: error.message 
      }));
    }
  };

  const switchChain = async (targetChainId: number) => {
    if (!window.ethereum) return;
    
    try {
      await window.ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: `0x${targetChainId.toString(16)}` }]
      });
    } catch (error) {
      if (error.code === 4902) {
        // Chain not added to wallet
        await addChainToWallet(targetChainId);
      }
    }
  };

  return (
    <WalletContext.Provider value={{
      ...state,
      connect,
      disconnect: () => setState({
        address: null,
        provider: null,
        chainId: null,
        isConnecting: false,
        error: null
      }),
      switchChain
    }}>
      {children}
    </WalletContext.Provider>
  );
};

Best Practices

  • Connection Persistence: Store connection state in localStorage for session persistence
  • Chain Validation: Always validate the connected network matches your dApp requirements
  • Graceful Degradation: Provide fallback options when preferred wallets aren't available
  • Loading States: Show clear loading indicators during connection attempts
  • Error Messages: Display user-friendly error messages with actionable solutions
  • Security Headers: Implement CSP headers to prevent wallet injection attacks
  • Rate Limiting: Implement connection attempt limits to prevent spam
  • Auto-reconnection: Attempt to reconnect on page reload if previously connected

Common Patterns

// Connection state management
const useWalletConnection = () => {
  useEffect(() => {
    const savedProvider = localStorage.getItem('selectedWalletProvider');
    if (savedProvider) {
      connect(savedProvider);
    }
  }, []);

  const connectAndSave = async (providerId: string) => {
    await connect(providerId);
    localStorage.setItem('selectedWalletProvider', providerId);
  };

  return { connectAndSave };
};

// Network switching with fallback
const ensureCorrectNetwork = async (targetChainId: number) => {
  if (chainId !== targetChainId) {
    await switchChain(targetChainId);
  }
};

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.