Learning Objectives:
-
Understand the complete Web3 architecture and development stack
-
Master frontend interaction with smart contracts using Web3.js and Ethers.js
-
Learn about wallets, providers, and authentication mechanisms
-
Build complete dApp workflows from frontend to blockchain
-
Understand deployment, testing, and best practices
4.6.1: What is Web3? The Evolution of the Internet
Web1, Web2, and Web3 – The Internet Evolution
The Three Eras of the Internet: Web1 (1990s - Early 2000s): "The Read-Only Web" - Static websites - No user interaction - Content consumed, not created - Centralized information - Examples: Early web pages, directories Web2 (2005 - Present): "The Read-Write Web" - User-generated content - Social media platforms - Interactivity and collaboration - Centralized platforms (Facebook, Google) - Data ownership by platforms Web3 (2020+): "The Read-Write-Own Web" - Decentralized applications - User ownership of data - Blockchain technology - Smart contracts - Token-based economics The Problem Web3 Solves: - Data ownership: Users own their data - Censorship resistance: Cannot be shut down - Trustless interaction: Code enforces agreements - Transparency: Open and verifiable - Value transfer: Native payments and tokens
Web3 Architecture Stack
Web3 Stack Architecture:
┌─────────────────────────────────────────────────────────────────────┐
│ Application Layer (Frontend) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ React/Vue │ │ Next.js │ │ Angular │ │
│ │ Components │ │ Framework │ │ Framework │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ Web3 Library Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Ethers.js │ │ Web3.js │ │ Wagmi │ │
│ │ Library │ │ Library │ │ (React) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ Wallet/Provider Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ MetaMask │ │ WalletConnect│ │ Coinbase │ │
│ │ (Browser) │ │ (Mobile) │ │ Wallet │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ RPC/API Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Infura │ │ Alchemy │ │ QuickNode │ │
│ │ (RPC) │ │ (RPC) │ │ (RPC) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ Blockchain Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Ethereum │ │ Polygon │ │ Arbitrum │ │
│ │ Mainnet │ │ (L2) │ │ (L2) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Key Differences: Web2 vs Web3 Development
| Aspect | Web2 | Web3 |
|---|---|---|
| Database | Centralized SQL/NoSQL | Blockchain (distributed) |
| Authentication | Username/Password | Wallet (private key) |
| Backend | Server-side code | Smart contracts |
| Frontend | React/Vue/Angular | React/Vue/Angular + Web3 |
| Payments | Payment processors (Stripe) | Native token transfers |
| Storage | Centralized servers | IPFS, Arweave |
| Identity | Platform-managed | Self-sovereign (DID) |
| State | Centralized server | On-chain state |
4.6.2: Web3 Libraries – Ethers.js vs Web3.js
Ethers.js Overview
Ethers.js is a complete Ethereum library with a focus on being lightweight, secure, and developer-friendly.
Ethers.js Key Features: 1. Provider Abstraction: - JSON-RPC providers - WebSocket providers - Fallback providers - Infura/Alchemy support 2. Wallet Management: - Private key wallets - HD wallets (Mnemonic) - Browser wallet (MetaMask) - JSON keystore 3. Contract Interaction: - Read functions (view/pure) - Write functions (transactions) - Event listening - Contract factory 4. Utilities: - BigNumber (BigInt) - Address utilities - ABI encoding/decoding - ENS support 5. TypeScript Ready: - Full type definitions - Type-safe interactions - Better IDE support Installation: npm install ethers
Web3.js Overview
Web3.js is the original Ethereum library with comprehensive features and broad adoption.
Web3.js Key Features: 1. Provider Integration: - HTTP providers - WebSocket providers - IPC providers 2. Modules: - web3.eth (Ethereum) - web3.eth.accounts - web3.eth.contract - web3.eth.personal 3. Utilities: - BN (BigNumber) - Utility functions - ABI encoding/decoding 4. Batch Requests: - Multiple requests - Efficiency - Reduced RPC calls Installation: npm install web3
Ethers.js vs Web3.js Comparison:
| Feature | Ethers.js | Web3.js |
|---|---|---|
| Size | Smaller (~350KB) | Larger (~700KB) |
| Big Numbers | BigInt (native) | BN (library) |
| TypeScript | First-class support | Limited |
| Provider | Flexible | Good |
| ENS | Built-in | Needs plugin |
| Learning Curve | Moderate | Moderate |
| Community | Growing | Large |
| Documentation | Good | Good |
| Active Development | Very active | Active |
Choosing the Right Library:
When to Use Ethers.js: - New projects - TypeScript projects - Need modern features - Want simpler API - Prefer native BigInt When to Use Web3.js: - Existing projects - Need full features - Want more flexibility - Familiar with Web3.js - Legacy codebase
4.6.3: Wallets and Providers – The Foundation
Understanding Ethereum Providers
A provider connects your dApp to the blockchain. It handles reading data and sending transactions.
Provider Types:
1. JSON-RPC Provider:
const provider = new ethers.JsonRpcProvider('http://localhost:8545');
// Connects to local node or remote RPC
2. WebSocket Provider:
const provider = new ethers.WebSocketProvider('wss://mainnet.infura.io/ws/v3/YOUR_KEY');
// Real-time updates
3. Browser Provider (MetaMask):
const provider = new ethers.BrowserProvider(window.ethereum);
// Uses MetaMask injected provider
4. Fallback Provider:
const provider = new ethers.FallbackProvider([
new ethers.InfuraProvider('mainnet', 'key1'),
new ethers.AlchemyProvider('mainnet', 'key2')
]);
// Multiple providers for redundancy
Connecting to MetaMask
MetaMask injects an ethereum object into the browser, allowing dApps to interact with the user’s wallet.
MetaMask Connection Flow:
1. Detect if MetaMask is installed
2. Request account access
3. Get connected accounts
4. Create provider and signer
5. Listen for account changes
Implementation:
// Detect MetaMask
if (typeof window.ethereum === 'undefined') {
console.log('MetaMask not installed');
// Show install prompt
}
// Connect to MetaMask
async function connectWallet() {
try {
// Request accounts (prompts user)
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
// Get first account
const account = accounts[0];
console.log('Connected:', account);
// Create provider
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
return { provider, signer, account };
} catch (error) {
console.error('Connection error:', error);
}
}
// Listen for account changes
window.ethereum.on('accountsChanged', (accounts) => {
console.log('Account changed:', accounts[0]);
// Update UI
});
// Listen for chain changes
window.ethereum.on('chainChanged', (chainId) => {
console.log('Chain changed:', chainId);
// Reload or update
});
WalletConnect Integration
WalletConnect enables mobile wallet integration through QR codes and deep links.
WalletConnect Implementation:
1. Install:
npm install @walletconnect/ethereum-provider
2. Setup:
import { EthereumProvider } from '@walletconnect/ethereum-provider';
async function connectWalletConnect() {
const provider = await EthereumProvider.init({
projectId: 'YOUR_PROJECT_ID',
chains: [1], // Ethereum mainnet
showQrModal: true,
methods: ['eth_sendTransaction', 'eth_sign'],
events: ['chainChanged', 'accountsChanged']
});
await provider.connect();
return provider;
}
3. Use with Ethers:
const provider = new ethers.BrowserProvider(walletConnectProvider);
const signer = await provider.getSigner();
Common Wallets:
| Wallet | Type | Features | Use Case |
|---|---|---|---|
| MetaMask | Browser/Mobile | Most popular, EIP-1559 | General dApps |
| WalletConnect | Protocol | Mobile support, QR | Mobile dApps |
| Coinbase Wallet | Mobile/Browser | Easy fiat on-ramp | User-friendly |
| Trust Wallet | Mobile | Multi-chain | Mobile dApps |
| Rainbow | Mobile | User-friendly | Consumer dApps |
| Phantom | Browser/Mobile | Solana + ETH | Multi-chain |
4.6.4: Contract Interaction – Reading and Writing
Reading Data (View/Pure Functions)
Reading data from a contract is free (no gas cost) and does not require a signer.
Reading Example:
// Contract ABI
const abi = [
"function balanceOf(address) view returns (uint256)",
"function totalSupply() view returns (uint256)",
"function name() view returns (string)",
"function symbol() view returns (string)"
];
const contractAddress = "0x..." // ERC-20 token address
// Create contract instance (no signer needed for reading)
const contract = new ethers.Contract(contractAddress, abi, provider);
// Read functions
async function readData() {
try {
// Total supply
const totalSupply = await contract.totalSupply();
console.log('Total Supply:', ethers.formatEther(totalSupply));
// Balance of address
const balance = await contract.balanceOf('0x...');
console.log('Balance:', ethers.formatEther(balance));
// Token info
const name = await contract.name();
const symbol = await contract.symbol();
console.log('Token:', name, '(', symbol, ')');
return { totalSupply, balance, name, symbol };
} catch (error) {
console.error('Read error:', error);
}
}
Writing Data (State-Changing Functions)
Writing data requires a transaction, gas, and a signer.
Writing Example:
// Create contract with signer
const signer = await provider.getSigner();
const contractWithSigner = contract.connect(signer);
async function writeData() {
try {
// Estimate gas
const gasEstimate = await contractWithSigner.transfer.estimateGas(
'0x...', // recipient
ethers.parseEther('1.0') // amount
);
console.log('Estimated gas:', gasEstimate.toString());
// Get gas price
const gasPrice = await provider.getGasPrice();
console.log('Current gas price:', ethers.formatUnits(gasPrice, 'gwei'), 'gwei');
// Send transaction
const tx = await contractWithSigner.transfer(
'0x...',
ethers.parseEther('1.0')
);
console.log('Transaction sent:', tx.hash);
// Wait for confirmation
const receipt = await tx.wait();
console.log('Transaction confirmed:', receipt);
// Check status