Back to Blockchain & Web3

Module 6: Web3 Frontend Development

Build user interfaces that interact with blockchain

📚 Web3 Libraries

To interact with Ethereum from JavaScript, you need a Web3 library. The two most popular are ethers.js and web3.js.

ethers.js ⭐ Recommended

Modern, lightweight, and well-documented. Most popular choice for new projects.

Smaller bundle size
Better TypeScript support
Excellent documentation
Active development
npm install ethers

web3.js

Older library, still widely used. Good for legacy projects.

Established ecosystem
Lots of tutorials
Compatible with older code
npm install web3

Recommendation: Use ethers.js for new projects. It's more modern, has better documentation, and is the industry standard for new dApps.

🦊 Connecting to MetaMask

The first step in any Web3 app is connecting to the user's wallet. Here's how to do it with ethers.js.

Basic Connection

import { ethers } from 'ethers';

// Check if MetaMask is installed
if (typeof window.ethereum !== 'undefined') {
  console.log('MetaMask is installed!');
}

// Request account access
async function connectWallet() {
  try {
    // Request accounts from MetaMask
    const accounts = await window.ethereum.request({ 
      method: 'eth_requestAccounts' 
    });
    
    console.log('Connected account:', accounts[0]);
    
    // Create provider
    const provider = new ethers.BrowserProvider(window.ethereum);
    
    // Get signer (account that can sign transactions)
    const signer = await provider.getSigner();
    
    // Get address
    const address = await signer.getAddress();
    console.log('Address:', address);
    
    // Get balance
    const balance = await provider.getBalance(address);
    console.log('Balance:', ethers.formatEther(balance), 'ETH');
    
    return { provider, signer, address };
  } catch (error) {
    console.error('Error connecting:', error);
  }
}

React Example

import { useState, useEffect } from 'react';
import { ethers } from 'ethers';

function WalletConnect() {
  const [account, setAccount] = useState(null);
  const [balance, setBalance] = useState(null);

  async function connectWallet() {
    if (typeof window.ethereum !== 'undefined') {
      try {
        // Request account access
        const accounts = await window.ethereum.request({ 
          method: 'eth_requestAccounts' 
        });
        setAccount(accounts[0]);
        
        // Get balance
        const provider = new ethers.BrowserProvider(window.ethereum);
        const balance = await provider.getBalance(accounts[0]);
        setBalance(ethers.formatEther(balance));
      } catch (error) {
        console.error('Error:', error);
      }
    } else {
      alert('Please install MetaMask!');
    }
  }

  // Listen for account changes
  useEffect(() => {
    if (window.ethereum) {
      window.ethereum.on('accountsChanged', (accounts) => {
        setAccount(accounts[0] || null);
      });
    }
  }, []);

  return (
    <div>
      {!account ? (
        <button onClick={connectWallet}>
          Connect Wallet
        </button>
      ) : (
        <div>
          <p>Connected: {account.slice(0, 6)}...{account.slice(-4)}</p>
          <p>Balance: {balance} ETH</p>
        </div>
      )}
    </div>
  );
}

💡 Best Practices:

  • • Always check if MetaMask is installed before connecting
  • • Handle account changes (user switches accounts)
  • • Handle network changes (user switches chains)
  • • Show clear error messages to users
  • • Never store private keys in your code!

📖 Reading Blockchain Data

Reading data from the blockchain is free (no gas cost). You can query balances, contract state, and call view functions.

import { ethers } from 'ethers';

// Connect to Ethereum (read-only, no wallet needed)
const provider = new ethers.JsonRpcProvider('https://eth.llamarpc.com');

// Get ETH balance
async function getBalance(address) {
  const balance = await provider.getBalance(address);
  return ethers.formatEther(balance);
}

// Get current block number
async function getBlockNumber() {
  return await provider.getBlockNumber();
}

// Get transaction details
async function getTransaction(txHash) {
  return await provider.getTransaction(txHash);
}

// Read from a smart contract
const contractAddress = '0x...';
const abi = [
  "function balanceOf(address owner) view returns (uint256)",
  "function totalSupply() view returns (uint256)"
];

const contract = new ethers.Contract(contractAddress, abi, provider);

// Call view functions (free, no gas)
async function getTokenBalance(address) {
  const balance = await contract.balanceOf(address);
  return balance.toString();
}

async function getTotalSupply() {
  const supply = await contract.totalSupply();
  return supply.toString();
}

🔑 Key Concepts:

  • Provider: Connection to Ethereum network (read-only)
  • Signer: Account that can sign transactions (write)
  • Contract: Interface to interact with smart contracts
  • ABI: Contract interface definition (what functions exist)

✍️ Sending Transactions

Writing to the blockchain requires a transaction, which costs gas and needs to be signed by the user.

Send ETH

async function sendETH(toAddress, amount) {
  try {
    const provider = new ethers.BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();
    
    // Send transaction
    const tx = await signer.sendTransaction({
      to: toAddress,
      value: ethers.parseEther(amount) // 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;
  } catch (error) {
    console.error('Error:', error);
  }
}

Interact with Smart Contract

// Contract ABI (simplified)
const abi = [
  "function transfer(address to, uint256 amount) returns (bool)",
  "function approve(address spender, uint256 amount) returns (bool)"
];

async function transferTokens(contractAddress, toAddress, amount) {
  try {
    const provider = new ethers.BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();
    
    // Create contract instance with signer (can write)
    const contract = new ethers.Contract(contractAddress, abi, signer);
    
    // Call function (sends transaction)
    const tx = await contract.transfer(toAddress, amount);
    console.log('Transaction sent:', tx.hash);
    
    // Wait for confirmation
    const receipt = await tx.wait();
    console.log('Transaction confirmed!');
    
    return receipt;
  } catch (error) {
    console.error('Error:', error);
    // Handle errors (user rejected, insufficient gas, etc.)
  }
}

⚠️ Important Notes:

  • • Always use parseEther() to convert ETH to wei
  • • Wait for transaction confirmation with tx.wait()
  • • Handle errors gracefully (user rejection, insufficient funds, etc.)
  • • Show transaction status to users (pending, confirmed, failed)

🌐 IPFS: Decentralized Storage

IPFS (InterPlanetary File System) is decentralized storage for files. Perfect for storing NFT images, metadata, and dApp assets.

Why IPFS?

Storing large files on blockchain is expensive. IPFS stores files off-chain but gives you a content-addressed hash (CID) that you can store on-chain.

  • • Content-addressed (hash of content, not location)
  • • Decentralized (no single point of failure)
  • • Permanent (content can't be changed)
  • • Efficient (deduplicated storage)
// Using Pinata (IPFS pinning service)
async function uploadToIPFS(file) {
  const formData = new FormData();
  formData.append('file', file);
  
  const response = await fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', {
    method: 'POST',
    headers: {
      'pinata_api_key': 'YOUR_API_KEY',
      'pinata_secret_api_key': 'YOUR_SECRET_KEY'
    },
    body: formData
  });
  
  const data = await response.json();
  const ipfsHash = data.IpfsHash;
  
  // Access file at: https://gateway.pinata.cloud/ipfs/{ipfsHash}
  return ipfsHash;
}

// Store IPFS hash in smart contract
async function mintNFT(ipfsHash) {
  const tokenURI = `ipfs://${ipfsHash}`;
  const tx = await nftContract.mint(tokenURI);
  await tx.wait();
}

📚 Learn More:

🎯 What's Next?

You now know how to build Web3 frontends! In the final module, we'll explore advanced topics like Layer 2 solutions, NFT marketplaces, DAOs, and the future of blockchain.