Skip to content
Web3 — Explained with Examples

Web3 — Explained with Examples

DodaTech Updated Jun 15, 2026 8 min read

Web3 is the decentralized internet model where users control their own data and digital assets through blockchain technology, enabling peer-to-peer interactions without intermediaries using wallets, smart contracts, and decentralized applications (dApps).

Why Web3 Matters

Web3 shifts power from centralized platforms back to users. Instead of Google owning your email and Facebook owning your social graph, you control your identity and data with a wallet. DodaTech is building Web3 features into Doda Browser — a built-in wallet, dApp browser, and decentralized file storage. The Web3 market is projected to grow to $40+ billion by 2030.

Web3 Architecture — How dApps Work

Think of Web3 like a vending machine network. The smart contract is the machine — it does exactly what it’s programmed to do. Your wallet is your money and ID card. The frontend is the button panel. When you press a button (click an action), the machine dispenses the result (executes the contract).

    graph TD
    subgraph Web3[<b>Web3 dApp Architecture</b>]
        FE[Frontend<br/>HTML, CSS, React/Vue]
        Wallet[Wallet<br/>MetaMask, WalletConnect]
        Provider[Provider<br/>JSON-RPC to blockchain]
        SC[Smart Contract<br/>Solidity, Vyper]
        BC[Blockchain<br/>Ethereum, Polygon]
        IPFS[IPFS<br/>Decentralized storage]
    end

    User[User] --> FE
    FE --> Wallet
    Wallet --> Provider
    Provider -->|Read call<br/>Free, no gas| SC
    Provider -->|Write call<br/>Costs gas| SC
    SC --> BC
    FE --> IPFS

    style Web3 fill:#3b82f6,color:#fff
  

Read vs Write Transactions

Web3 has two fundamentally different types of blockchain interactions.

// read_vs_write.js
const { ethers } = require("ethers");

async function demonstrateReadWrite() {
  const provider = new ethers.providers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/demo");

  // READ — query blockchain state, free, no gas
  const blockNumber = await provider.getBlockNumber();
  const balance = await provider.getBalance("vitalik.eth");

  console.log("=== READ Transactions (Free) ===");
  console.log("Current block:", blockNumber);
  console.log("Vitalik's ETH balance:", ethers.utils.formatEther(balance));

  // READ a contract — using a read-only provider
  const daiAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
  const abi = ["function balanceOf(address) view returns (uint256)"];
  const dai = new ethers.Contract(daiAddress, abi, provider);
  const vitalikBalance = await dai.balanceOf("vitalik.eth");
  console.log("Vitalik's DAI balance:", ethers.utils.formatEther(vitalikBalance));

  // WRITE — needs a signer (private key) and costs gas
  console.log("\n=== WRITE Transactions (Costs Gas) ===");
  console.log("To write, you need:");
  console.log("  1. A signer (private key or MetaMask)");
  console.log("  2. ETH for gas");
  console.log("  3. A transaction to send");

  // Simulated write (not executed — needs real signer)
  console.log("\nExample write (not executed):");
  console.log("const tx = await contract.transfer('0x...', amount);");
  console.log("await tx.wait(); // Wait for confirmation");
  console.log("console.log('Transaction mined:', tx.hash);");
}

demonstrateReadWrite();

Expected output:

=== READ Transactions (Free) ===
Current block: 20123456
Vitalik's ETH balance: 1234.5678
Vitalik's DAI balance: 5678.9012

=== WRITE Transactions (Costs Gas) ===
To write, you need:
  1. A signer (private key or MetaMask)
  2. ETH for gas
  3. A transaction to send

Example write (not executed):
const tx = await contract.transfer('0x...', amount);
await tx.wait(); // Wait for confirmation
console.log('Transaction mined:', tx.hash);

Wallet Integration — MetaMask

MetaMask is the most popular Web3 wallet. Your dApp connects to it through the browser’s window.ethereum object.

<!DOCTYPE html>
<html>
<head>
  <title>DodaTech dApp — Wallet Connect</title>
  <style>
    body { font-family: sans-serif; padding: 2rem; }
    button { padding: 12px 24px; font-size: 16px; }
    #status { margin-top: 1rem; padding: 1rem; background: #f5f5f5; border-radius: 8px; }
  </style>
</head>
<body>
  <h1>DodaTech dApp Demo</h1>
  <button id="connect-btn">Connect MetaMask</button>
  <div id="status">Not connected</div>

<script>
const connectBtn = document.getElementById('connect-btn');
const status = document.getElementById('status');

async function connectWallet() {
  if (typeof window.ethereum === 'undefined') {
    status.textContent = 'Please install MetaMask!';
    status.style.background = '#fee2e2';
    return;
  }

  try {
    // Request account access
    const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
    const account = accounts[0];

    // Get network info
    const chainId = await window.ethereum.request({ method: 'eth_chainId' });
    const networkNames = {
      '0x1': 'Ethereum Mainnet',
      '0x5': 'Goerli Testnet',
      '0xaa36a7': 'Sepolia Testnet',
      '0x89': 'Polygon',
      '0x13881': 'Mumbai'
    };

    // Get balance
    const balance = await window.ethereum.request({
      method: 'eth_getBalance',
      params: [account, 'latest']
    });
    const ethBalance = parseInt(balance, 16) / 1e18;

    status.innerHTML = `
      <strong>Connected!</strong><br>
      Account: ${account.substring(0, 6)}...${account.substring(38)}<br>
      Network: ${networkNames[chainId] || 'Unknown (' + chainId + ')'}<br>
      Balance: ${ethBalance.toFixed(4)} ETH
    `;
    status.style.background = '#dcfce7';

    // Listen for account changes
    window.ethereum.on('accountsChanged', (accounts) => {
      console.log('Account changed to:', accounts[0]);
      window.location.reload();
    });

  } catch (error) {
    console.error('Connection error:', error);
    status.textContent = 'Connection rejected: ' + error.message;
    status.style.background = '#fee2e2';
  }
}

connectBtn.addEventListener('click', connectWallet);
</script>
</body>
</html>

Web3.js — Interacting with Ethereum

Web3.js is the original JavaScript library for Ethereum interaction.

// web3js_demo.js
const Web3 = require("web3");

async function web3jsExample() {
  const web3 = new Web3("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY");

  // Get account balances
  const accounts = [
    "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // vitalik.eth
    "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"  // VB2
  ];

  console.log("=== Account Balances ===");
  for (const addr of accounts) {
    const balance = await web3.eth.getBalance(addr);
    console.log(`${addr.substring(0, 10)}... → ${web3.utils.fromWei(balance, "ether")} ETH`);
  }

  // Get transaction details
  const txHash = "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b";
  try {
    const tx = await web3.eth.getTransaction(txHash);
    console.log("\n=== Transaction Details ===");
    console.log("From:", tx.from);
    console.log("To:", tx.to);
    console.log("Value:", web3.utils.fromWei(tx.value, "ether"), "ETH");
    console.log("Gas price:", web3.utils.fromWei(tx.gasPrice, "gwei"), "Gwei");
  } catch {
    console.log("Transaction not found (demo hash)");
  }

  // Create contract instance
  const abi = [{"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"}];
  const contract = new web3.eth.Contract(abi, "0x6B175474E89094C44Da98b954EedeAC495271d0F");

  const totalSupply = await contract.methods.totalSupply().call();
  console.log("\nDAI Total Supply:", web3.utils.fromWei(totalSupply, "ether"));
}

web3jsExample();

Expected output:

=== Account Balances ===
0xd8dA6BF26... → 1234.5678 ETH
0xAb5801a7D... → 890.1234 ETH

=== Transaction Details ===
From: 0x1234...abcd
To: 0x5678...ef01
Value: 10.5 ETH
Gas price: 25 Gwei

DAI Total Supply: 5000000000.0

Ethers.js — The Modern Alternative

ethers.js is lighter and more developer-friendly than Web3.js.

// ethersjs_demo.js
const { ethers } = require("ethers");

async function ethersjsExample() {
  // Connecting to Ethereum
  const provider = new ethers.providers.JsonRpcProvider(
    "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
  );

  // ENS resolution (name to address)
  const vitalikAddress = await provider.resolveName("vitalik.eth");
  console.log("vitalik.eth →", vitalikAddress);

  // Reverse resolution (address to name)
  const name = await provider.lookupAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
  console.log("Address →", name);

  // Formatting utilities
  const weiAmount = ethers.utils.parseEther("1.5");
  console.log("\n1.5 ETH =", weiAmount.toString(), "wei");

  const ethAmount = ethers.utils.formatEther("1500000000000000000");
  console.log("1,500,000,000,000,000,000 wei =", ethAmount, "ETH");

  // Contract interaction
  const contractABI = [
    "function balanceOf(address) view returns (uint256)",
    "function symbol() view returns (string)"
  ];
  const usdcContract = new ethers.Contract(
    "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    contractABI,
    provider
  );

  const symbol = await usdcContract.symbol();
  const whales = ["0x55FE002aefF02F77364de339a1292923A15844B8", "0x47ac0Fb4F2D84898e04D9e62b9F7C3cbF6A5b3d9"];

  console.log(`\n=== ${symbol} Whale Balances ===`);
  for (const address of whales) {
    const bal = await usdcContract.balanceOf(address);
    console.log(`${address.substring(0, 10)}... → ${ethers.utils.formatUnits(bal, 6)} ${symbol}`);
  }
}

ethersjsExample();

Expected output:

vitalik.eth → 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Address → vitalik.eth

1.5 ETH = 1500000000000000000 wei
1,500,000,000,000,000,000 wei = 1.5 ETH

=== USDC Whale Balances ===
0x55FE002ae... → 250,000,000 USDC
0x47ac0Fb4F... → 180,500,000 USDC

Sending a Transaction with Ethers.js

// send_transaction.js
const { ethers } = require("ethers");

async function sendTransaction() {
  // Connect to Sepolia testnet
  const provider = new ethers.providers.JsonRpcProvider(
    "https://sepolia.infura.io/v3/YOUR_PROJECT_ID"
  );

  // NEVER expose private keys in code! Use environment variables.
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    console.log("Set PRIVATE_KEY environment variable");
    console.log("\nSimulating transaction flow...\n");
    simulateTransaction();
    return;
  }

  const wallet = new ethers.Wallet(privateKey, provider);
  const balance = await wallet.getBalance();
  console.log("Wallet balance:", ethers.utils.formatEther(balance), "ETH");

  // Send ETH to another address
  const tx = await wallet.sendTransaction({
    to: "0xRecipientAddressHere",
    value: ethers.utils.parseEther("0.01")
  });

  console.log("Transaction sent:", tx.hash);
  console.log("Waiting for confirmation...");

  const receipt = await tx.wait();
  console.log("Confirmed in block:", receipt.blockNumber);
  console.log("Gas used:", receipt.gasUsed.toString());
}

function simulateTransaction() {
  const tx = {
    to: "0xRecipientAddress",
    value: ethers.utils.parseEther("0.01"),
    gasLimit: 21000,
    gasPrice: ethers.utils.parseUnits("25", "gwei"),
    nonce: 42,
    chainId: 11155111 // Sepolia
  };

  const estimatedCost = tx.gasLimit * tx.gasPrice;
  console.log("=== Transaction Simulation ===");
  console.log("To:", tx.to);
  console.log("Amount: 0.01 ETH");
  console.log("Gas limit:", tx.gasLimit);
  console.log("Gas price: 25 Gwei");
  console.log("Max fee:", ethers.utils.formatEther(estimatedCost), "ETH");
  console.log("Total cost: 0.01" + " + " + ethers.utils.formatEther(estimatedCost) + " = " +
    ethers.utils.formatEther(ethers.utils.parseEther("0.01").add(estimatedCost)) + " ETH");
}

sendTransaction();

Expected output (without PRIVATE_KEY):

Set PRIVATE_KEY environment variable

Simulating transaction flow...

=== Transaction Simulation ===
To: 0xRecipientAddress
Amount: 0.01 ETH
Gas limit: 21000
Gas price: 25 Gwei
Max fee: 0.000525 ETH
Total cost: 0.010525 ETH

dApp Project Structure

my-dapp/
├── contracts/
│   └── MyNFT.sol
├── scripts/
│   ├── deploy.js
│   └── mint.js
├── frontend/
│   ├── index.html
│   ├── app.js
│   └── style.css
├── test/
│   └── MyNFT.test.js
├── hardhat.config.js
├── package.json
└── README.md

Common Mistakes

  1. Not handling network changes: Users switch networks in MetaMask. Listen for chainChanged events and update your dApp accordingly.
  2. Exposing private keys in client-side code: Never put private keys in frontend code. Use MetaMask for user transactions and environment variables for backend.
  3. Not waiting for transaction confirmations: Calling contract.method without await tx.wait() means you’re checking state before the transaction is mined.
  4. Using Mainnet for testing: Always start on Sepolia or Hardhat local network. Mainnet mistakes cost real money.
  5. Not handling wallet disconnection: Users can disconnect MetaMask. Your dApp should gracefully handle missing wallets and prompt reconnection.

Practice Questions

  1. What is the difference between a read and a write transaction?
  2. What does window.ethereum provide in a dApp?
  3. How does an ENS name like “vitalik.eth” resolve to an address?
  4. What is the purpose of tx.wait() after sending a transaction?
  5. Why should you never put private keys in frontend JavaScript?

Answers:

  1. Read transactions query blockchain state without modifying it — they’re free. Write transactions modify state (transfer tokens, mint NFTs) and cost gas.
  2. window.ethereum is the provider injected by MetaMask. It provides access to the user’s accounts, allows signing transactions, and connects to the blockchain.
  3. ENS resolves human-readable names to addresses via a smart contract. provider.resolveName('vitalik.eth') looks up the ENS contract and returns the associated address.
  4. tx.wait() waits for the transaction to be mined and included in a block. Without it, you’d check state before the transaction is confirmed.
  5. Private keys give full control of the wallet. Client-side code is visible to users. Exposure would let anyone steal all funds. Always use MetaMask or a backend signer.

Mini Project: Build a dApp that Reads and Writes

Build a complete dApp that:

  1. Shows a “Connect Wallet” button (MetaMask integration)
  2. Detects network and account changes
  3. Reads the current ETH balance of the connected wallet
  4. Displays the latest block number
  5. Reads a value from a deployed smart contract
  6. Allows the user to write to the contract (e.g., set a message)
  7. Shows the transaction status (pending, confirmed, failed)
  8. Works on Sepolia testnet

This is the foundation DodaTech uses for browser-based dApps integrated into Doda Browser’s Web3 features.

Related topics: Ethereum, Smart Contracts, NFTs, DeFi, Layer 2

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro