Outils·7 min de lecture·Par Solingo

MetaMask for Developers — Connect Your dApp to Web3

MetaMask is the most popular Web3 wallet with 30+ million users. Learn how to detect, connect, and interact with MetaMask to build seamless dApp experiences.

# MetaMask for Developers — Connect Your dApp to Web3

MetaMask is the gateway to Web3 for millions of users. As the most popular Ethereum wallet browser extension, it allows users to interact with decentralized applications (dApps) directly from their browser. For developers, integrating MetaMask is essential to enable users to connect wallets, sign transactions, and interact with smart contracts. This guide covers everything you need to know to build a seamless MetaMask integration.

What is MetaMask?

MetaMask is a cryptocurrency wallet and gateway to blockchain applications. It functions as:

  • Browser extension (Chrome, Firefox, Edge, Brave)
  • Mobile app (iOS and Android)
  • Web3 provider injecting window.ethereum into web pages
  • Account manager for Ethereum addresses and private keys
  • Network switcher between Ethereum mainnet, testnets, and other EVM chains

With over 30 million monthly active users, MetaMask is the default wallet for most Ethereum dApps.

How MetaMask Works

When installed, MetaMask injects a JavaScript object called window.ethereum (or window.ethereum via EIP-6963) into every webpage. Your dApp uses this object to:

  • Detect if MetaMask is installed
  • Request user permission to connect
  • Read the connected account address
  • Send transaction requests
  • Listen for account/network changes
  • All sensitive operations (signing transactions, messages) happen inside MetaMask's secure UI — your dApp never has access to private keys.

    Detecting MetaMask

    Basic Detection

    if (typeof window.ethereum !== 'undefined') {
    

    console.log('MetaMask is installed!');

    } else {

    console.log('Please install MetaMask');

    // Redirect to https://metamask.io/download

    }

    EIP-6963: Multi-Wallet Detection

    Modern dApps should support multiple wallets. EIP-6963 provides a standard way to detect all installed wallets:

    // Listen for wallet providers
    

    const wallets = [];

    window.addEventListener('eip6963:announceProvider', (event) => {

    wallets.push(event.detail);

    });

    // Request wallet announcements

    window.dispatchEvent(new Event('eip6963:requestProvider'));

    // wallets now contains all installed providers (MetaMask, Coinbase Wallet, etc.)

    Checking if MetaMask is the Active Provider

    const isMetaMask = window.ethereum?.isMetaMask;
    

    if (isMetaMask) {

    console.log('MetaMask detected');

    }

    Connecting to MetaMask

    Request Account Access

    async function connectWallet() {
    

    try {

    // Request account access

    const accounts = await window.ethereum.request({

    method: 'eth_requestAccounts'

    });

    const account = accounts[0];

    console.log('Connected account:', account);

    return account;

    } catch (error) {

    if (error.code === 4001) {

    // User rejected the request

    console.log('Please connect to MetaMask');

    } else {

    console.error('Error connecting:', error);

    }

    }

    }

    Get Connected Accounts (No Popup)

    If the user has already connected, use eth_accounts (doesn't show popup):

    async function getConnectedAccounts() {
    

    const accounts = await window.ethereum.request({

    method: 'eth_accounts'

    });

    return accounts; // Empty array if not connected

    }

    Complete Connection Flow

    async function init() {
    

    // Check if already connected

    const accounts = await window.ethereum.request({ method: 'eth_accounts' });

    if (accounts.length > 0) {

    // Already connected

    setAccount(accounts[0]);

    } else {

    // Show "Connect Wallet" button

    document.getElementById('connectButton').addEventListener('click', async () => {

    const newAccounts = await window.ethereum.request({

    method: 'eth_requestAccounts'

    });

    setAccount(newAccounts[0]);

    });

    }

    }

    function setAccount(account) {

    document.getElementById('account').textContent = account;

    document.getElementById('connectButton').style.display = 'none';

    document.getElementById('dapp').style.display = 'block';

    }

    Sending Transactions

    Send ETH

    async function sendEther(to, amount) {
    

    const accounts = await window.ethereum.request({ method: 'eth_accounts' });

    const from = accounts[0];

    // amount in wei (1 ETH = 10^18 wei)

    const value = '0x' + (amount * 10**18).toString(16);

    try {

    const txHash = await window.ethereum.request({

    method: 'eth_sendTransaction',

    params: [{

    from: from,

    to: to,

    value: value,

    // gas: '0x5208', // Optional: 21000 wei for simple transfer

    }],

    });

    console.log('Transaction hash:', txHash);

    return txHash;

    } catch (error) {

    console.error('Transaction failed:', error);

    }

    }

    // Usage

    await sendEther('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 0.1);

    Interact with Smart Contracts (Raw)

    async function callContractFunction() {
    

    const contractAddress = '0x...';

    const accounts = await window.ethereum.request({ method: 'eth_accounts' });

    // Function signature: transfer(address,uint256)

    const functionSignature = '0xa9059cbb'; // First 4 bytes of keccak256("transfer(address,uint256)")

    // Encode parameters (address + amount)

    const recipient = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'.slice(2).padStart(64, '0');

    const amount = (100 * 10**18).toString(16).padStart(64, '0');

    const data = functionSignature + recipient + amount;

    const txHash = await window.ethereum.request({

    method: 'eth_sendTransaction',

    params: [{

    from: accounts[0],

    to: contractAddress,

    data: data,

    }],

    });

    return txHash;

    }

    Note: Manual encoding is error-prone. Use ethers.js or web3.js (see below).

    Switching Networks

    Request Network Switch

    async function switchToSepolia() {
    

    try {

    await window.ethereum.request({

    method: 'wallet_switchEthereumChain',

    params: [{ chainId: '0xaa36a7' }], // Sepolia testnet

    });

    } catch (error) {

    if (error.code === 4902) {

    // Network not added to MetaMask

    await addSepoliaNetwork();

    } else {

    console.error('Failed to switch network:', error);

    }

    }

    }

    Add Custom Network

    async function addPolygonNetwork() {
    

    try {

    await window.ethereum.request({

    method: 'wallet_addEthereumChain',

    params: [{

    chainId: '0x89', // 137 in decimal

    chainName: 'Polygon Mainnet',

    nativeCurrency: {

    name: 'MATIC',

    symbol: 'MATIC',

    decimals: 18

    },

    rpcUrls: ['https://polygon-rpc.com'],

    blockExplorerUrls: ['https://polygonscan.com']

    }],

    });

    } catch (error) {

    console.error('Failed to add network:', error);

    }

    }

    Get Current Network

    async function getCurrentNetwork() {
    

    const chainId = await window.ethereum.request({ method: 'eth_chainId' });

    console.log('Current chain ID:', chainId); // e.g., "0x1" for Ethereum mainnet

    const networkMap = {

    '0x1': 'Ethereum Mainnet',

    '0xaa36a7': 'Sepolia Testnet',

    '0x89': 'Polygon Mainnet',

    '0xa4b1': 'Arbitrum One',

    };

    return networkMap[chainId] || 'Unknown Network';

    }

    Listening for Changes

    Account Changes

    window.ethereum.on('accountsChanged', (accounts) => {
    

    if (accounts.length === 0) {

    // User disconnected all accounts

    console.log('Please connect to MetaMask');

    } else {

    // User switched account

    console.log('Account changed to:', accounts[0]);

    setAccount(accounts[0]);

    }

    });

    Network Changes

    window.ethereum.on('chainChanged', (chainId) => {
    

    console.log('Network changed to:', chainId);

    // Recommended: reload the page to avoid inconsistent state

    window.location.reload();

    });

    Connection/Disconnection

    window.ethereum.on('connect', (connectInfo) => {
    

    console.log('Connected to network:', connectInfo.chainId);

    });

    window.ethereum.on('disconnect', (error) => {

    console.log('Disconnected from network:', error);

    });

    Integrating with Ethers.js

    Raw MetaMask API is verbose. Ethers.js provides a cleaner abstraction.

    Installation

    npm install ethers

    Connect Wallet with Ethers.js

    import { BrowserProvider } from 'ethers';
    
    

    async function connectWallet() {

    if (typeof window.ethereum === 'undefined') {

    alert('Please install MetaMask');

    return;

    }

    // Request account access

    await window.ethereum.request({ method: 'eth_requestAccounts' });

    // Create ethers provider

    const provider = new BrowserProvider(window.ethereum);

    // Get signer (connected account)

    const signer = await provider.getSigner();

    const address = await signer.getAddress();

    console.log('Connected:', address);

    return { provider, signer, address };

    }

    Send Transaction with Ethers.js

    async function sendEther(to, amount) {
    

    const { signer } = await connectWallet();

    const tx = await signer.sendTransaction({

    to: to,

    value: ethers.parseEther(amount.toString()) // Convert ETH to wei

    });

    console.log('Transaction sent:', tx.hash);

    // Wait for confirmation

    const receipt = await tx.wait();

    console.log('Transaction confirmed:', receipt);

    return receipt;

    }

    // Usage

    await sendEther('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 0.1);

    Interact with Smart Contracts

    import { Contract } from 'ethers';
    
    

    async function interactWithContract() {

    const { signer } = await connectWallet();

    const contractAddress = '0x...';

    const abi = [

    "function transfer(address to, uint256 amount) returns (bool)",

    "function balanceOf(address owner) view returns (uint256)"

    ];

    const contract = new Contract(contractAddress, abi, signer);

    // Read function (no gas cost)

    const balance = await contract.balanceOf(await signer.getAddress());

    console.log('Balance:', ethers.formatEther(balance));

    // Write function (costs gas)

    const tx = await contract.transfer('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', ethers.parseEther('10'));

    await tx.wait();

    console.log('Transfer complete');

    }

    Read Blockchain Data

    async function getBlockchainData() {
    

    const provider = new BrowserProvider(window.ethereum);

    // Get block number

    const blockNumber = await provider.getBlockNumber();

    console.log('Current block:', blockNumber);

    // Get gas price

    const feeData = await provider.getFeeData();

    console.log('Gas price:', ethers.formatUnits(feeData.gasPrice, 'gwei'), 'gwei');

    // Get balance

    const balance = await provider.getBalance('0x...');

    console.log('Balance:', ethers.formatEther(balance), 'ETH');

    }

    Signing Messages

    Personal Sign (Human-Readable Messages)

    async function signMessage(message) {
    

    const accounts = await window.ethereum.request({ method: 'eth_accounts' });

    const signature = await window.ethereum.request({

    method: 'personal_sign',

    params: [message, accounts[0]],

    });

    console.log('Signature:', signature);

    return signature;

    }

    // Usage

    await signMessage('I agree to the terms of service');

    With Ethers.js

    async function signMessage(message) {
    

    const { signer } = await connectWallet();

    const signature = await signer.signMessage(message);

    return signature;

    }

    EIP-712: Typed Structured Data Signing

    For structured data (used in permit functions, gasless transactions):

    async function signTypedData() {
    

    const { signer } = await connectWallet();

    const domain = {

    name: 'MyDApp',

    version: '1',

    chainId: 1,

    verifyingContract: '0x...'

    };

    const types = {

    Transfer: [

    { name: 'to', type: 'address' },

    { name: 'amount', type: 'uint256' }

    ]

    };

    const value = {

    to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',

    amount: ethers.parseEther('10')

    };

    const signature = await signer.signTypedData(domain, types, value);

    return signature;

    }

    Error Handling

    Common Error Codes

    async function handleMetaMaskError() {
    

    try {

    await window.ethereum.request({ method: 'eth_requestAccounts' });

    } catch (error) {

    switch (error.code) {

    case 4001:

    // User rejected the request

    console.log('Please connect to MetaMask');

    break;

    case -32002:

    // Request already pending

    console.log('Please open MetaMask to complete the request');

    break;

    case -32603:

    // Internal error

    console.error('Internal error:', error.message);

    break;

    default:

    console.error('Unknown error:', error);

    }

    }

    }

    Best Practices

    1. Always Check for MetaMask

    if (typeof window.ethereum === 'undefined') {
    

    showInstallPrompt(); // Guide user to install MetaMask

    }

    2. Handle Account Changes

    window.ethereum.on('accountsChanged', (accounts) => {
    

    if (accounts.length === 0) {

    logout(); // User disconnected

    } else {

    updateAccount(accounts[0]);

    }

    });

    3. Reload on Network Change

    window.ethereum.on('chainChanged', () => {
    

    window.location.reload(); // Prevents inconsistent state

    });

    4. Request Permissions Once

    Don't call eth_requestAccounts on every page load. Use eth_accounts to check if already connected.

    5. Provide Clear Error Messages

    if (error.code === 4001) {
    

    alert('You need to connect your wallet to use this feature');

    }

    6. Test on Multiple Networks

    Test your dApp on mainnet, testnets (Sepolia), and L2s (Polygon, Arbitrum).

    7. Support Mobile

    MetaMask Mobile uses WalletConnect for deep linking. Test on mobile browsers.

    Complete React Example

    import { useState, useEffect } from 'react';
    

    import { BrowserProvider } from 'ethers';

    function App() {

    const [account, setAccount] = useState(null);

    const [provider, setProvider] = useState(null);

    useEffect(() => {

    if (window.ethereum) {

    // Check if already connected

    window.ethereum.request({ method: 'eth_accounts' })

    .then(accounts => {

    if (accounts.length > 0) {

    setAccount(accounts[0]);

    setProvider(new BrowserProvider(window.ethereum));

    }

    });

    // Listen for account changes

    window.ethereum.on('accountsChanged', (accounts) => {

    setAccount(accounts[0] || null);

    });

    // Listen for network changes

    window.ethereum.on('chainChanged', () => {

    window.location.reload();

    });

    }

    }, []);

    const connectWallet = async () => {

    if (!window.ethereum) {

    alert('Please install MetaMask');

    return;

    }

    const accounts = await window.ethereum.request({

    method: 'eth_requestAccounts'

    });

    setAccount(accounts[0]);

    setProvider(new BrowserProvider(window.ethereum));

    };

    return (

    <div>

    {account ? (

    <div>

    <p>Connected: {account}</p>

    {/* Your dApp UI */}

    </div>

    ) : (

    <button onClick={connectWallet}>Connect Wallet</button>

    )}

    </div>

    );

    }

    Conclusion

    MetaMask integration is essential for any Ethereum dApp. By following this guide, you can detect MetaMask, connect wallets, send transactions, and interact with smart contracts seamlessly. Combined with ethers.js, you have a powerful toolkit to build Web3 applications that millions of users can access.

    Start building your dApp today — integrate MetaMask and bring your smart contracts to life. Practice wallet integration with Solingo's hands-on Web3 projects to master full-stack blockchain development.

    Next steps:

    • Build a simple dApp with wallet connection
    • Implement transaction history tracking
    • Add support for multiple wallets (Coinbase Wallet, WalletConnect)
    • Learn about gasless transactions with EIP-2612 permits

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement