Full DApp Project — Web3

Full DApp Project Saatnya menggabungkan semua yang sudah dipelajari menjadi DApp (Decentralized Application) end-to-end. Kita akan membahas arsitektur lengkap…

DApp lengkap adalah aplikasi yang logika dan data pentingnya ada di smart contract, dipakai lewat wallet pengguna, ditampilkan oleh frontend, dengan file besar di IPFS dan riwayat yang dirangkum indexer. Di lesson ini semua bagian dari lesson 1 sampai 17 dirakit jadi satu proyek: Toko Kaos Raka. Bayangin hari pembukaan toko. Ada kasir yang aturannya gak bisa diubah (contract), kartu member di dompet pelanggan (wallet), etalase (frontend), gudang foto katalog (IPFS), buku rekap bulanan (indexer), dan ruang kantor untuk hal yang gak boleh dipajang di buku publik, seperti alamat pengiriman (server sendiri).

🧪 Tebakan cepat: Raka men-deploy KaosEdisiKopi ke Sepolia, baru setelah itu meng-upload metadata KOPI ke IPFS dan dapat CID-nya. Bisakah CID itu dipasang ke contract yang sudah jalan? Tebak dulu, jawabannya ada di bagian "Urutan membangun".

Arsitektur Toko Kaos Raka

Bagian Isinya Alat
Smart contract PoinRaka (ERC-20, 1 POIN per Rp 1.000, 100 POIN = potongan Rp 5.000) dan KaosEdisiKopi (ERC-721, maksimal 100) Solidity, OpenZeppelin 5.6.1, Hardhat 3.19.0
Wallet kunci pelanggan; menandatangani redeem MetaMask atau wallet EIP-6963 lain
Frontend halaman "POIN kamu" dan tombol tukar React, wagmi 3.7.7, viem 2.57.4
IPFS metadata dan gambar KOPI, ditunjuk lewat ipfs:// kubo, layanan pinning
Indexer ranking pemegang POIN, riwayat Transfer, pemilik KOPI subgraph (The Graph) atau Ponder
Server sendiri (off-chain) alamat pengiriman, notifikasi, cache backend biasa, tanpa private key pengguna

Alurnya: frontend membaca lewat RPC atau indexer, wallet menandatangani transaksi, contract mengubah state dan memancarkan event, indexer membaca event itu, dan frontend menampilkan hasilnya.

Checklist lesson 1 sampai 17

# Slug lesson Yang dipakai di proyek ini
1 blockchain-fundamentals data on-chain publik dan permanen, jadi data pribadi tetap off-chain
2 ethereum-smart-contracts akun, gas, chain lokal 31337 dan Sepolia 11155111
3 wallet-metamask-integration menghubungkan wallet, pindah chain, login SIWE
4 solidity-basics state, event, custom error di PoinRaka
5 ethers-js-viem client, calldata, parseUnits('100', 18)
6 reading-blockchain-data eth_call, getLogs, latest vs finalized
7 sending-transactions receipt, revert tetap bayar gas, simulasi dulu
8 erc20-token PoinRaka versi ERC-20, approve secukupnya
9 erc721-nft KaosEdisiKopi, _safeMint, SoldOut() di #101
10 dapp-frontend-wagmi-react komponen "POIN kamu" dengan wagmi
11 smart-contract-testing-hardhat 11 test, fixture, coverage
12 ipfs-decentralized-storage folder metadata kopi/ dan CID-nya di _baseURI
13 defi-basics-untuk-devs belum dipakai toko ini; perlu kalau POIN masuk kolam AMM (slippage, oracle)
14 gas-optimization ukur gas, optimizer untuk deploy
15 smart-contract-security access control, Slither, audit
16 the-graph-indexing subgraph untuk Account, Transfer dan Kaos
17 l2-scaling-solutions pilihan deploy ke L2 yang lebih murah

Urutan membangun

  1. Contract + test di chain lokal (lesson 4, 8, 9, 11): npx hardhat test harus hijau, termasuk jalur gagal.
  2. Metadata ke IPFS (lesson 12): upload folder kopi/, catat CID-nya, tulis di _baseURI.
  3. Deploy lokal + script alur toko (lesson 7): bagian berikut.
  4. Frontend ke chain lokal (lesson 10), alamat contract dibaca dari file hasil deploy.
  5. Indexer (lesson 16) mulai dari block deploy.
  6. Testnet: Sepolia, atau L2 testnet seperti OP Sepolia (lesson 17), lalu verifikasi source code.
  7. Persiapan mainnet: bagian "Sebelum mainnet".

Kenapa IPFS sebelum deploy? _baseURI di KaosEdisiKopi adalah fungsi pure yang mengembalikan string tetap, dan contract yang sudah di-deploy gak bisa diedit. CID yang baru didapat setelah deploy gak bisa dipasang; Raka harus deploy ulang dengan alamat baru. Itu jawaban tebakannya.

Jalankan semuanya di chain lokal

// scripts/deploy.ts: deploy PoinRaka dan KaosEdisiKopi, lalu simpan alamatnya untuk script, frontend dan subgraph
import { writeFileSync } from "node:fs";
import { network } from "hardhat";
import { getAddress } from "viem";

const { viem, networkName } = await network.getOrCreate();
const publicClient = await viem.getPublicClient();
const [raka] = await viem.getWalletClients();

const poin = await viem.sendDeploymentTransaction("PoinRaka");
const poinReceipt = await publicClient.waitForTransactionReceipt({ hash: poin.deploymentTransaction.hash });
const kopi = await viem.sendDeploymentTransaction("KaosEdisiKopi");
const kopiReceipt = await publicClient.waitForTransactionReceipt({ hash: kopi.deploymentTransaction.hash });

console.log(`network ${networkName}, chain ID ${await publicClient.getChainId()}, deployer ${getAddress(raka.account.address)}`);
console.log(`PoinRaka      ${poin.contract.address}  block ${poinReceipt.blockNumber}  gas ${poinReceipt.gasUsed}`);
console.log(`KaosEdisiKopi ${kopi.contract.address}  block ${kopiReceipt.blockNumber}  gas ${kopiReceipt.gasUsed}`);

writeFileSync(
  `deployments/${networkName}.json`,
  JSON.stringify({ PoinRaka: poin.contract.address, KaosEdisiKopi: kopi.contract.address, startBlock: Number(poinReceipt.blockNumber) }, null, 2),
);

Script kedua, scripts/toko.ts, membaca deployments/localhost.json, lalu menjalankan satu hari di toko: empat belanja (earn), mint KOPI #1 untuk Dinda dan #2 untuk Yoga, Dinda menukar 100 POIN, dan Yoga mencoba menukar 200 lewat simulate (jadi transaksi yang pasti gagal gak dikirim). Ini output sungguhan dari Hardhat 3.19.0, chain lokal yang baru (akun test publik, gak ada uang asli):

$ npx hardhat test
11 passing (2 solidity, 9 nodejs)

$ npx hardhat node                       # terminal lain: chain lokal 31337
$ npx hardhat run scripts/deploy.ts --network localhost
network localhost, chain ID 31337, deployer 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
PoinRaka      0x5FbDB2315678afecb367f032d93F642f64180aa3  block 1  gas 1229456
KaosEdisiKopi 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  block 2  gas 1999101

$ npx hardhat run scripts/toko.ts --network localhost
block 3  earn(Dinda, 95000)  Kaos Sablon Kopi gas 73216  success
block 4  earn(Yoga, 75000)   Kaos Polos Hitam gas 56104  success
block 5  earn(Yoga, 40000)   Topi Raka        gas 38992  success
block 6  earn(Dinda, 65000)  Mug Sablon Kopi  gas 39004  success
block 7  mint KOPI #1 untuk Dinda             gas 96559  success
block 8  mint KOPI #2 untuk Yoga              gas 79447  success
block 9  Dinda redeem 100 POIN                gas 35679  success
Error: ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed)
                               (0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC, 115000000000000000000, 200000000000000000000)
POIN Dinda 60, Yoga 115, totalSupply 175
KOPI #1 milik 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (Dinda)
tokenURI(1) = ipfs://bafybeifuwwkzdzittkxf5ghlmsq2k6jqasyheos6mb3vgz4l73qcmsgfku/1
Transfer PoinRaka: 5 log, head block 9

Frontend, indexer, dan L2

Frontend memakai komponen "POIN kamu" dari lesson dapp-frontend-wagmi-react apa adanya; yang berubah cuma daftar chain dan alamat:

import { createConfig, http } from 'wagmi'
import { hardhat, sepolia, optimismSepolia } from 'wagmi/chains'
import { injected } from 'wagmi/connectors'

export const config = createConfig({
  chains: [hardhat, sepolia, optimismSepolia],
  connectors: [injected()],
  transports: { [hardhat.id]: http(), [sepolia.id]: http(), [optimismSepolia.id]: http() },
})

Subgraph dari lesson the-graph-indexing ditambah satu data source untuk KOPI (alamat 0xe7f1...0512, startBlock: 2, event Transfer(indexed address,indexed address,indexed uint256)) dengan entity Kaos { id, owner, mintedAtBlock }; graph build dan test Matchstick-nya lulus. Untuk L2, tambahkan network opSepolia di hardhat.config.ts (lesson l2-scaling-solutions), jalankan npx hardhat run scripts/deploy.ts --network opSepolia, dan deployments/opSepolia.json terisi alamatnya. Simpan alamat per chain ID, jangan satu konstanta.

Kapan butuh server sendiri

Contract cocok untuk aturan yang harus dipercaya semua orang: siapa punya berapa POIN, siapa pemilik KOPI #1. Server biasa (off-chain) tetap dibutuhkan untuk:

Yang gak boleh: menyimpan private key pengguna atau menandatangani atas nama mereka. Itu tugas wallet.

Sebelum mainnet

Tutorial ini berhenti di testnet. Kalau suatu hari contract ini memegang nilai sungguhan:

⚠️ Jebakan yang sering ketemu

TL;DR: DApp lengkap Toko Kaos Raka terdiri dari smart contract (PoinRaka ERC-20 dan KaosEdisiKopi ERC-721) sebagai backend yang dipercaya, wallet untuk menandatangani, frontend React + wagmi, metadata di IPFS, indexer untuk ranking dan riwayat, dan server biasa untuk data yang gak perlu on-chain, notifikasi dan cache (tanpa pernah memegang private key pengguna). Urutannya: contract + test, metadata IPFS (CID harus ada sebelum deploy karena _baseURI gak bisa diganti), deploy lokal + script, frontend, indexer, testnet (Sepolia atau L2 testnet) + verifikasi. Di chain lokal: 11 test lulus, PoinRaka di 0x5FbD...0aa3, KaosEdisiKopi di 0xe7f1...0512, Dinda tinggal 60 POIN setelah redeem, Yoga ditolak ERC20InsufficientBalance di simulasi. Sebelum mainnet: audit, verifikasi source, kunci owner di hardware wallet atau multisig.

Yang akan kamu pelajari