पाठ 23 / 25

Building dApp Front Ends

Connect wallets, read state and send transactions from a web app.

The user-facing side

A decentralised application (dApp) front end is a normal web app (often React or Next.js) that talks to contracts through the user's wallet and an RPC provider. Libraries such as wagmi (React hooks built on viem) with connectors like RainbowKit, ConnectKit or Reown AppKit handle wallet connection (browser extensions, mobile wallets via WalletConnect, and smart wallets), network switching, reading contract state, simulating and sending transactions, and waiting for confirmations. Good dApp UX: show clear transaction states (awaiting signature, pending, confirmed, failed), decode and explain revert reasons (custom errors help here), display amounts with correct decimals, warn on wrong networks, request the minimum approval needed, and never ask for seed phrases. Index events for history and lists using services such as The Graph, Ponder or your own indexer, rather than scanning the chain from the browser. Treat all on-chain data as public. Sign-in with Ethereum (SIWE, EIP-4361) lets users authenticate to a backend with a signed message. Host front ends securely: compromised front ends and DNS hijacks have been used to steal funds.

Reading and writing with wagmi hooks

Connect, read a balance and send a deposit with clear states.

import { useAccount, useConnect, useReadContract, useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { injected } from "wagmi/connectors";
import { parseEther, formatEther } from "viem";
import { vaultAbi } from "./abi";

const VAULT = "0xYourVaultAddress" as const;

export function VaultPanel() {
  const { address, isConnected } = useAccount();
  const { connect } = useConnect();
  const { data: balance, refetch } = useReadContract({
    address: VAULT, abi: vaultAbi, functionName: "balances",
    args: address ? [address] : undefined,
    query: { enabled: Boolean(address) },
  });
  const { writeContract, data: hash, isPending, error } = useWriteContract();
  const { isLoading: confirming, isSuccess } = useWaitForTransactionReceipt({ hash });

  if (!isConnected) return <button onClick={() => connect({ connector: injected() })}>Connect wallet</button>;

  return (
    <div>
      <p>Your vault balance: {balance !== undefined ? formatEther(balance) : "..."} ETH</p>
      <button
        disabled={isPending || confirming}
        onClick={() => writeContract({ address: VAULT, abi: vaultAbi, functionName: "deposit", value: parseEther("0.01") })}
      >
        {isPending ? "Confirm in wallet..." : confirming ? "Waiting for confirmation..." : "Deposit 0.01 ETH"}
      </button>
      {isSuccess && <p>Deposit confirmed. <button onClick={() => refetch()}>Refresh</button></p>}
      {error && <p role="alert">Transaction failed: {error.message}</p>}
    </div>
  );
}

A shop counter and a customer's own wallet

The dApp is the shop counter showing prices and preparing the bill; the user's wallet is their own purse. The shop never holds the purse: it hands over a bill (transaction) and the customer decides whether to sign and pay.

त्वरित जाँच: Why should a dApp index events with a service rather than scanning the chain in the browser?

  • Events cannot be read
  • Scanning many blocks from the browser is slow and rate-limited; indexers provide fast, queryable history
  • Indexers make transactions free
  • Browsers cannot connect to RPC nodes
Answer

Scanning many blocks from the browser is slow and rate-limited; indexers provide fast, queryable history — Indexers process events once and serve them efficiently.