Real-World DApps Examples

Real-World DApps: Patterns, Flows & Integration

From token swaps to crowdfunding: on-chain contracts + front-end + wallet + indexers.

TL;DR: A DApp = Smart contract + Front-end + Wallet + RPC/Indexers. Learn common flows you can implement and test end-to-end.

1) DApp Architecture

Real apps are more than a contract. You’ll stitch together a UI (React/Next.js), a wallet (EIP-1193 provider), an RPC endpoint, one or more contracts, and often an indexer and decentralized storage. Your job: keep the user’s private keys safe, keep state in sync, and make transactions understandable before they sign.

Frontend (Next.js/React) ── Wallet (EIP-1193: MetaMask/Rabby)
        │
        ├─ RPC Provider (Alchemy/Infura/Ankr)
        │
        ├─ Smart Contracts (Solidity, OpenZeppelin)
        │
        └─ Indexing (The Graph/Goldsky) + Storage (IPFS/Arweave)

Minimum front-end loop:

  1. Detect provider (window.ethereum) and chain; prompt connect (read-only works without connect).
  2. Read contract state (free): balances, allowances, supply, prices.
  3. Prepare write: gather params; show a human-readable summary (amounts, slippage, deadlines).
  4. Send tx; show pending state with hash + link to explorer; handle confirmation and errors.
  5. Re-query state or subscribe to events; optimistic update the UI.
// Pseudo-TS with EIP-1193
const provider = new window.ethereum.constructor(); // or window.ethereum
await provider.request({ method: "eth_requestAccounts" });
const chainId = await provider.request({ method: "eth_chainId" });
// Read: call via eth_call
// Write: provider.request({ method:"eth_sendTransaction", params:[{ to, data, value }] })
UX checklist: clear network indicator • copyable addresses • fiat estimates • gas fee preview • explicit risks (slippage/approvals) • disabled buttons while pending • undo paths (revoke approvals, cancel listings).

2) Token Swap (AMM)

A classic swap flow touches approvals, quotes, and execution. Users rarely understand “allowance,” so your UI must explain and bound it. You’ll also manage slippage and route choice (direct pool vs. router/aggregator).

  • Allowances: ERC-20s require the user to approve a spender (router) to transfer their tokens. Prefer an allowance equal to amountIn (not unlimited) for safety, or ask the user explicitly if they want “infinite” to avoid re-approvals.
  • Quotes: Compute expected amountOut from pool reserves (x*y=k) or call a router’s getAmountsOut. Show minOut and slippage in the UI.
  • Tx sequencing: (1) approve, wait confirm → (2) swap with minOut and deadline.
// Pseudo flow
approve(tokenIn, router, amountIn) // bounded
router.swapExactTokensForTokens(tokenIn, tokenOut, amountIn, minOut, path)

Front-end pseudo (read → approve → swap):

// 1) Read allowance and quote
const allowance = await erc20.allowance(user, router);
const quote = await router.getAmountsOut(amountIn, [tokenIn, tokenOut]);

// 2) If allowance < amountIn, prompt approve
if (allowance < amountIn) {
  await erc20.approve(router, amountIn); // or limited + permit (EIP-2612)
}

// 3) Execute swap with safety bounds
const minOut = quote * (1 - slippageBps/10_000);
await router.swapExactTokensForTokens(amountIn, minOut, [tokenIn, tokenOut], user, deadline);

Security & UX:

  • Always validate minOut and deadline in the contract or router call. Without these, MEV/sandwich risk increases.
  • Show allowance manager and revoke link post-swap; nudge users to revoke large/unused approvals.
  • If using permit signatures (EIP-2612), display a clear “Grant spend permission” step with token name and spender.

3) NFT Mint (ERC-721)

A clean mint flow involves content storage, metadata, mint logic, and a front-end that previews what the user gets. Decide up front: fixed supply vs. open edition, price, allowlist, royalties, and reveal mechanics.

  • Metadata & media: Upload JSON + media to IPFS/Arweave. Your contract’s tokenURI returns a base + id (e.g., ipfs://...).
  • Supply controls: Cap supply; emit events on mint; consider reserve function for the team but gate it by role and timelock.
  • Payments: Simple fixed-price sale: require(msg.value == price). Withdrawals go to a treasury; prefer pull payments and avoid auto-splitting inline.
  • Allowlist: If needed, use a Merkle root to prove membership with per-wallet caps.
  • Royalties: Expose ERC-2981 royaltyInfo (marketplaces read this).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";

contract SimpleMint is ERC721, ERC2981, Ownable {
    uint256 public total;
    uint256 public constant MAX = 1000;
    uint256 public price = 0.05 ether;
    string private _base;

    constructor(string memory base, address royaltyRecv, uint96 bps)
      ERC721("SimpleMint","SMT")
    { _base = base; _setDefaultRoyalty(royaltyRecv, bps); }

    function _baseURI() internal view override returns(string memory){ return _base; }

    function mint(uint256 qty) external payable {
        require(qty > 0, "qty");
        require(total + qty <= MAX, "Sold out");
        require(msg.value == price * qty, "price");
        for (uint i; i < qty; ++i) _safeMint(msg.sender, ++total);
    }

    function supportsInterface(bytes4 iid)
      public view override(ERC721, ERC2981) returns (bool) { return super.supportsInterface(iid); }
}

Front-end UX: show remaining supply, price in ETH + fiat, live preview from tokenURI, and a post-mint button to view on an explorer/marketplace. If reveal is delayed, show a placeholder image and “reveal in X days.”

Testing notes: Fork tests to ensure tokenURI resolves via gateways; add invariant “total ≤ MAX”; fuzz payment amounts; assert royalties are returned as expected by royaltyInfo.

4) Crowdfunding (Escrow)

An escrowed campaign has a simple state machine: Funding → Succeeded/Failed. Funds stay locked until success; contributors can withdraw if it fails. When you deal with money flows, re-entrancy safety and explicit events are non-negotiable.

Funds lock until goal/time; refunds if not met; owner can withdraw on success.

// Key checks: goal reached? deadline passed? prevent re-entrancy.
require(block.timestamp < deadline, "Ended");
require(address(this).balance + msg.value <= hardCap, "Cap");

Sketch of core logic:

  • contribute(): accepts ETH while now < deadline and raised <= hardCap. Track per-user contributions. Emit Contributed(user, amount).
  • claimRefund(): enabled if now ≥ deadline and raised < goal. Use pull payments (set contributor’s balance to 0, then send). Add nonReentrant.
  • withdrawToOwner(): enabled if raised ≥ goal after deadline. Only owner/recipient can pull funds.
  • cancel(): optional admin cancel before any contributions; or add a guardian role to halt on incident.

Front-end UX: timeline component (start → deadline), progress bar with % of goal, contributor list (by indexing events), buttons that change state based on now, goal, raised. On refund, disable after success and show tx link.

Edge cases to test: last-block contributions near deadline • over-fund protection • re-entrancy on refund/withdraw • emergency pause • returning dust • repeated refunds (should be idempotent).

5) Multisig & UX

Most treasuries and high-value contracts should not be controlled by a single key. A 2/3 or 3/5 multisig dramatically reduces single-point-of-failure risk and gives you review windows for privileged actions.

Multi-party control for treasuries (e.g., 2/3). Use Safe (Gnosis) for production; integrate with WalletConnect.

  • Propose → Confirm → Execute: One signer proposes a transaction (target, value, data). Co-signers confirm. Once threshold is met, anyone can execute.
  • Front-end touches: display pending transactions, who signed, required threshold, and a “simulate” view of what the tx will do (decode calldata).
  • Timelocks: For upgrades/parameter changes, pair multisig with a timelock so the community can review before it takes effect.
  • Key hygiene: Each signer should use a hardware wallet. For teams, separate roles: payout approvers vs. upgraders vs. pausers.

Integration tip: If your DApp interacts with a multisig-owned contract, expose helper buttons like “Compose call for Safe” that pre-fills the target/data so signers don’t copy/paste raw hex.

Quick check

  1. Why do DEX swaps require token approvals?
  2. Where should NFT metadata live and why?
  3. What’s the benefit of multisig vs single-owner?
Show answers
  • Routers need an ERC-20 allowance to transfer tokens on the user’s behalf; approvals bound spend and protect users.
  • IPFS/Arweave for immutability and decentralization so assets/metadata remain accessible and tamper-resistant.
  • Removes single-key failure, enforces review, and enables role separation (threshold signatures).

Go deeper

Before deploying, understand common smart-contract risks.

Next: Risks (Re-entrancy, Oracles, etc.) →