Episode 6 — Scaling Reliability Microservices Web3 / 6.10 — Web3 Basics

6.10.c — Decentralized Applications (DApps)

In one sentence: A DApp (Decentralized Application) is an application where the backend logic runs on a blockchain via smart contracts instead of centralized servers, enabling censorship-resistant, transparent, and user-owned digital experiences.

Navigation: <- 6.10.b — Blockchain Fundamentals | 6.10.d — Smart Contracts ->


1. What Are DApps?

A Decentralized Application (DApp) is an application that runs on a decentralized network rather than a single server. The key difference from traditional applications: the backend logic and state live on a blockchain, not on servers you control.

What makes an app a DApp:

  1. OPEN SOURCE     — Code is publicly verifiable
  2. DECENTRALIZED   — Runs on a blockchain (no single point of failure)
  3. TOKEN-BASED     — Uses crypto tokens for access, governance, or value
  4. CONSENSUS       — Changes require network agreement

What does NOT make an app a DApp:
  - Having a crypto payment option (that's just a feature)
  - Using blockchain for logging (that's just an audit trail)
  - Having a token that does nothing (that's marketing)

2. Architecture Comparison: Traditional App vs DApp

This is the most important mental model shift for backend engineers.

Traditional Application Architecture

┌──────────────────────────────────────────────────────────┐
│  TRADITIONAL WEB APPLICATION                              │
│                                                           │
│  ┌──────────┐    HTTP/REST    ┌──────────────┐           │
│  │ Frontend  │ ──────────────>│   Backend     │           │
│  │ (React)   │ <──────────────│   (Express)   │           │
│  └──────────┘    JSON         └──────┬───────┘           │
│                                      │                    │
│                                      │ SQL/ORM            │
│                                      ▼                    │
│                                ┌──────────────┐           │
│                                │  Database     │           │
│                                │  (PostgreSQL) │           │
│                                └──────────────┘           │
│                                                           │
│  Controlled by: ONE company                               │
│  Data stored: Company's servers                           │
│  Authentication: Username + password                      │
│  Payments: Stripe / PayPal                                │
└──────────────────────────────────────────────────────────┘

DApp Architecture

┌──────────────────────────────────────────────────────────┐
│  DECENTRALIZED APPLICATION (DApp)                         │
│                                                           │
│  ┌──────────┐   ethers.js    ┌──────────────┐            │
│  │ Frontend  │ ──────────────>│   Blockchain  │           │
│  │ (React)   │ <──────────────│   (Ethereum)  │           │
│  └────┬─────┘   JSON-RPC     └──────┬───────┘            │
│       │                              │                    │
│       │                              │ EVM executes       │
│       │                              ▼                    │
│       │                        ┌──────────────┐           │
│       │                        │Smart Contract │           │
│       │                        │  (Solidity)   │           │
│       │                        └──────────────┘           │
│       │                                                   │
│       │  ┌────────────┐        ┌──────────────┐           │
│       └─>│   Wallet    │        │    IPFS      │           │
│          │ (MetaMask)  │        │ (file storage)│          │
│          └────────────┘        └──────────────┘           │
│                                                           │
│  Controlled by: NO single entity                          │
│  Data stored: Blockchain + IPFS (distributed)             │
│  Authentication: Wallet signature                         │
│  Payments: Native crypto                                  │
└──────────────────────────────────────────────────────────┘

Side-by-Side Comparison

AspectTraditional AppDApp
BackendExpress/Django on serversSmart contracts on blockchain
DatabasePostgreSQL/MongoDBBlockchain state
File storageS3, local diskIPFS, Arweave
AuthenticationJWT, sessions, OAuthWallet signature (cryptographic)
PaymentsStripe, PayPalNative crypto (ETH, tokens)
HostingAWS, Vercel, HerokuBlockchain nodes (decentralized)
Frontend hostingVercel, NetlifyIPFS, Vercel (often still centralized)
UpdatesRedeploy serverDeploy new contract (old one remains)
DowntimeServer outage = app downBlockchain nodes always running
Data privacyPrivate by defaultPublic by default (on-chain)
Cost modelServer costs (monthly)Gas fees (per transaction)
SpeedMillisecondsSeconds to minutes

3. Wallet Connection (MetaMask)

In Web3, users authenticate with a wallet instead of a username/password. The wallet holds the user's private keys and signs transactions.

How Wallet Authentication Works

TRADITIONAL LOGIN:                 WEB3 WALLET LOGIN:

  1. User types email/password      1. User clicks "Connect Wallet"
  2. Server checks database         2. MetaMask pops up
  3. Server creates JWT              3. User approves connection
  4. Client stores JWT               4. DApp reads wallet address
  5. JWT sent with each request      5. For actions: user SIGNS with private key

  Trust: Server knows your password  Trust: Only you have your private key
  Risk: Server breach = data leak    Risk: Phishing, user error

Connecting to MetaMask with ethers.js

import { ethers } from 'ethers';

// Check if MetaMask is installed
if (typeof window.ethereum === 'undefined') {
  console.log('Please install MetaMask!');
} else {
  // Request account access
  const provider = new ethers.BrowserProvider(window.ethereum);
  
  // This triggers the MetaMask popup asking user to connect
  const signer = await provider.getSigner();
  const address = await signer.getAddress();
  
  console.log('Connected wallet:', address);
  // Output: "Connected wallet: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
  
  // Get the user's ETH balance
  const balance = await provider.getBalance(address);
  console.log('Balance:', ethers.formatEther(balance), 'ETH');
}

Signing Messages (Proof of Identity)

// The user signs a message to prove they own the wallet
// This does NOT cost gas — it's just a cryptographic signature

const message = 'Sign in to MyDApp at 2024-03-15T14:23:07Z';
const signature = await signer.signMessage(message);

console.log('Signature:', signature);
// Output: "0x3a4b5c..." (65-byte signature)

// Anyone can verify this signature was made by the wallet owner
const recoveredAddress = ethers.verifyMessage(message, signature);
console.log('Signer:', recoveredAddress);
// Output matches the connected wallet address

4. Web3.js and Ethers.js Libraries

Two JavaScript libraries dominate blockchain interaction. Ethers.js is the modern standard.

Ethers.js vs Web3.js

Featureethers.jsWeb3.js
Bundle size~120 KB (small)~590 KB (large)
DesignModular, clean APIMonolithic
TypeScriptFirst-class supportAdded later
Provider/SignerSeparated (good practice)Combined
PopularityGrowing (recommended)Legacy (still widely used)
DocumentationExcellentGood
Maintained byRichard MooreChainSafe

Key Concepts in ethers.js

import { ethers } from 'ethers';

// ========================================
// PROVIDER — read-only connection to blockchain
// ========================================
// Think of it like a "database read connection"

// Connect to Ethereum mainnet via a node provider
const provider = new ethers.JsonRpcProvider(
  'https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'
);

// Read-only operations (no wallet needed):
const blockNumber = await provider.getBlockNumber();
const balance = await provider.getBalance('0xAddress...');
const network = await provider.getNetwork();

// ========================================
// SIGNER — wallet that can sign transactions
// ========================================
// Think of it like a "database write connection"

// From a browser wallet (MetaMask)
const browserProvider = new ethers.BrowserProvider(window.ethereum);
const signer = await browserProvider.getSigner();

// From a private key (backend/scripts — NEVER expose private keys!)
const wallet = new ethers.Wallet('0xPRIVATE_KEY', provider);

// ========================================
// CONTRACT — interact with a smart contract
// ========================================
// Think of it like an "ORM model"

const contractAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // USDC
const abi = [
  'function balanceOf(address owner) view returns (uint256)',
  'function transfer(address to, uint256 amount) returns (bool)',
  'event Transfer(address indexed from, address indexed to, uint256 value)'
];

// Read-only contract instance
const readContract = new ethers.Contract(contractAddress, abi, provider);
const usdcBalance = await readContract.balanceOf('0xSomeAddress...');

// Read-write contract instance (needs a signer)
const writeContract = new ethers.Contract(contractAddress, abi, signer);
const tx = await writeContract.transfer('0xRecipient...', ethers.parseUnits('100', 6));
await tx.wait(); // Wait for transaction confirmation

5. Reading Blockchain Data

Reading data from the blockchain is free (no gas costs). It's the equivalent of a SELECT query.

import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/KEY');

// ----- Read basic blockchain data -----
const block = await provider.getBlock('latest');
console.log('Block number:', block.number);
console.log('Timestamp:', new Date(block.timestamp * 1000));
console.log('Transactions:', block.transactions.length);

// ----- Read a specific transaction -----
const tx = await provider.getTransaction('0xTransactionHash...');
console.log('From:', tx.from);
console.log('To:', tx.to);
console.log('Value:', ethers.formatEther(tx.value), 'ETH');

// ----- Read transaction receipt (after confirmation) -----
const receipt = await provider.getTransactionReceipt('0xTransactionHash...');
console.log('Status:', receipt.status === 1 ? 'Success' : 'Failed');
console.log('Gas used:', receipt.gasUsed.toString());

// ----- Listen for new blocks (real-time) -----
provider.on('block', (blockNumber) => {
  console.log('New block:', blockNumber);
});

// ----- Listen for contract events -----
const contract = new ethers.Contract(contractAddress, abi, provider);
contract.on('Transfer', (from, to, value) => {
  console.log(`Transfer: ${from} -> ${to}: ${ethers.formatUnits(value, 6)} USDC`);
});

6. IPFS for Decentralized Storage

IPFS (InterPlanetary File System) is a peer-to-peer file storage protocol. Instead of storing files on a single server, IPFS distributes them across a network of nodes.

TRADITIONAL FILE STORAGE:              IPFS:

  https://myserver.com/image.png        ipfs://QmX3a8b...7c9d
       │                                     │
       ▼                                     ▼
  ┌──────────┐                         ┌───┐ ┌───┐ ┌───┐
  │ One server│                         │ N1│ │ N2│ │ N3│
  │ (AWS S3)  │                         └───┘ └───┘ └───┘
  └──────────┘                         Any node with the file
                                       can serve it

  Server goes down = file gone          Content-addressable:
  URL can change                        The hash IS the address
  Censorable                            Cannot be censored

Why DApps Use IPFS

Storing large files (images, videos, metadata) directly on the blockchain is prohibitively expensive (~$20,000 per MB on Ethereum). DApps store large data on IPFS and only store the IPFS hash on-chain.

NFT storage pattern:

  On-chain (smart contract):
    tokenURI = "ipfs://QmX3a8b...7c9d"   ← only the hash (32 bytes, cheap)

  On IPFS (decentralized storage):
    {
      "name": "Cool NFT #42",
      "description": "A very cool digital artwork",
      "image": "ipfs://QmY4b9c...8d0e",   ← image stored on IPFS too
      "attributes": [
        { "trait_type": "Color", "value": "Blue" },
        { "trait_type": "Rarity", "value": "Legendary" }
      ]
    }

7. DApp Categories

CategoryWhat It DoesExamples
DeFiDecentralized finance — lending, borrowing, tradingUniswap, Aave, Compound, MakerDAO
NFT MarketplacesBuy, sell, and trade NFTsOpenSea, Blur, Magic Eden
GamingPlay-to-earn, on-chain game assetsAxie Infinity, Gods Unchained
SocialDecentralized social mediaLens Protocol, Farcaster
IdentitySelf-sovereign identity, domainsENS (Ethereum Name Service)
DAOsCommunity governanceUniswap Governance, Gitcoin
InfrastructureDeveloper tools, indexingThe Graph, Chainlink (oracles)
StorageDecentralized file/data storageIPFS, Filecoin, Arweave

8. Advantages and Disadvantages of DApps

Advantages

AdvantageExplanation
Censorship resistanceNo single entity can shut down the app or block users
TransparencyAll code and transactions are publicly verifiable
No downtimeBlockchain networks run 24/7 (no server maintenance windows)
User ownershipUsers own their data and assets (tokens, NFTs)
ComposabilityDApps can integrate with other DApps seamlessly ("money legos")
Global accessAnyone with internet + wallet can participate
TrustlessUsers don't need to trust the developers — the code is the law

Disadvantages

DisadvantageExplanation
Poor UXWallets, gas fees, and confirmations are confusing for normal users
SlowBlockchain confirmations take seconds to minutes (vs milliseconds for servers)
ExpensiveEvery write operation costs gas; costs spike during congestion
Immutable bugsDeployed smart contract bugs cannot be easily patched
Limited storageOn-chain storage is extremely expensive
ScalabilityCurrent blockchains handle far fewer transactions than centralized systems
IrreversibleMistakes (wrong address, lost keys) cannot be undone
Regulatory riskUncertain legal frameworks across jurisdictions
Key managementLosing your private key means losing everything — no "forgot password"

9. Current DApp Ecosystem

DApp Ecosystem by TVL (Total Value Locked) — approximate figures:

  DeFi Protocols:
    Lido (liquid staking):        ~$30B TVL
    Aave (lending/borrowing):     ~$15B TVL
    MakerDAO (stablecoins):       ~$8B TVL
    Uniswap (decentralized exchange): ~$6B TVL
    Eigenlayer (restaking):       ~$10B TVL

  By Blockchain:
    Ethereum:   ~60% of all DeFi TVL
    Solana:     ~8%
    BSC:        ~5%
    Arbitrum:   ~5%
    Polygon:    ~3%
    Others:     ~19%

  Active DApp users (daily unique wallets):
    ~5-10 million daily active wallets across all chains
    Compare to: Facebook ~2 billion daily active users

10. Simple DApp Interaction Example

Here is a complete example of interacting with a DApp (Uniswap-style token swap) from a frontend:

import { ethers } from 'ethers';

// ========================================
// Step 1: Connect wallet
// ========================================
async function connectWallet() {
  if (!window.ethereum) {
    throw new Error('No wallet found. Install MetaMask.');
  }
  
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const address = await signer.getAddress();
  const balance = await provider.getBalance(address);
  
  return {
    provider,
    signer,
    address,
    balance: ethers.formatEther(balance)
  };
}

// ========================================
// Step 2: Read data from a smart contract
// ========================================
async function getTokenBalance(provider, tokenAddress, walletAddress) {
  const abi = [
    'function balanceOf(address) view returns (uint256)',
    'function decimals() view returns (uint8)',
    'function symbol() view returns (string)'
  ];
  
  const contract = new ethers.Contract(tokenAddress, abi, provider);
  
  const [balance, decimals, symbol] = await Promise.all([
    contract.balanceOf(walletAddress),
    contract.decimals(),
    contract.symbol()
  ]);
  
  return {
    raw: balance,
    formatted: ethers.formatUnits(balance, decimals),
    symbol,
    decimals
  };
}

// ========================================
// Step 3: Send a transaction (costs gas!)
// ========================================
async function sendToken(signer, tokenAddress, recipientAddress, amount) {
  const abi = [
    'function transfer(address to, uint256 amount) returns (bool)',
    'function decimals() view returns (uint8)'
  ];
  
  const contract = new ethers.Contract(tokenAddress, abi, signer);
  const decimals = await contract.decimals();
  const amountInWei = ethers.parseUnits(amount.toString(), decimals);
  
  console.log(`Sending ${amount} tokens to ${recipientAddress}...`);
  
  // This triggers MetaMask to ask for user approval
  const tx = await contract.transfer(recipientAddress, amountInWei);
  console.log('Transaction hash:', tx.hash);
  
  // Wait for confirmation
  const receipt = await tx.wait();
  console.log('Confirmed in block:', receipt.blockNumber);
  console.log('Gas used:', receipt.gasUsed.toString());
  
  return receipt;
}

// ========================================
// Step 4: Listen for events
// ========================================
async function listenForTransfers(provider, tokenAddress) {
  const abi = [
    'event Transfer(address indexed from, address indexed to, uint256 value)'
  ];
  
  const contract = new ethers.Contract(tokenAddress, abi, provider);
  
  contract.on('Transfer', (from, to, value, event) => {
    console.log(`Transfer detected!`);
    console.log(`  From: ${from}`);
    console.log(`  To: ${to}`);
    console.log(`  Value: ${value.toString()}`);
    console.log(`  Block: ${event.log.blockNumber}`);
  });
  
  console.log('Listening for transfer events...');
}

// ========================================
// Usage
// ========================================
async function main() {
  const { provider, signer, address, balance } = await connectWallet();
  console.log(`Connected: ${address} (${balance} ETH)`);
  
  const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
  
  // Read USDC balance
  const usdc = await getTokenBalance(provider, USDC_ADDRESS, address);
  console.log(`USDC Balance: ${usdc.formatted} ${usdc.symbol}`);
  
  // Send 10 USDC (user approves in MetaMask)
  // await sendToken(signer, USDC_ADDRESS, '0xRecipient...', 10);
  
  // Listen for all USDC transfers
  await listenForTransfers(provider, USDC_ADDRESS);
}

main().catch(console.error);

11. Key Takeaways

  1. DApps replace centralized backends with smart contracts — the blockchain is both your server and database, with the trade-off of speed and cost for decentralization and censorship resistance.
  2. Architecture shift — traditional apps use REST APIs and databases; DApps use ethers.js, smart contracts, and blockchain state. Frontends remain similar (React).
  3. Wallet = identity — MetaMask and similar wallets replace username/password authentication with cryptographic signatures.
  4. ethers.js is the standard library — use Providers for reading, Signers for writing, and Contracts for interacting with smart contracts.
  5. Reading is free, writing costs gas — every state-changing transaction costs gas, which varies by complexity and network congestion.
  6. IPFS solves the storage problem — large files go on IPFS, only the content hash is stored on-chain.
  7. Real trade-offs exist — DApps are slower, more expensive, and harder to use than traditional apps, but they offer censorship resistance, transparency, and user ownership.

Explain-It Challenge

  1. Your product manager asks "why can't we just use a normal database instead of a blockchain for our DApp?" Give three scenarios where a blockchain genuinely adds value and three where it does not.
  2. A user reports that their MetaMask transaction has been "pending" for 30 minutes. Explain what happened and how to fix it.
  3. Design the architecture for a decentralized marketplace (like OpenSea). Which parts live on-chain, which on IPFS, and which might still need centralized infrastructure?

Navigation: <- 6.10.b — Blockchain Fundamentals | 6.10.d — Smart Contracts ->