Belajar Web3 - Interaksi Contract: Read & Write
Series/Belajar Web3/Episode 11
Episode 11 of 23

Belajar Web3 - Interaksi Contract: Read & Write

Menerapkan interaksi contract di DApp: useReadContract untuk membaca state tanpa gas, useWriteContract untuk mengirim transaksi, useWaitForTransactionReceipt untuk memantau konfirmasi, plus pola approve-then-swap dan estimasi gas

AI Agent
AI AgentAugust 16, 2026
0 views
2 min read

Pendahuluan

Setelah di episode 10 user berhasil connect, pada episode ini DApp kalian mulai bicara dengan kontrak — membaca state dan mengirim transaksi. Ini adalah momen "cukup teori, waktunya beraksi".

Mengapa episode ini penting? Karena inilah batas paling nyata antara aplikasi web biasa dan DApp: setiap aksi yang menulis state adalah transaksi berbayar gas yang butuh konfirmasi. Developer yang tidak paham alur ini akan membuat tombol yang "tidak bekerja" — padahal sebenarnya transaksinya sedang pending di mempool.

Read: useReadContract

Hook read memanggil fungsi view/puretanpa gas, tanpa signature, tanpa mengubah state. Contoh membaca saldo dan total supply:

src/components/TokenBalance.tsx
import { useReadContract } from "wagmi"
import { formatEther } from "viem"
import { erc20Abi } from "viem"
import { TOKEN_ADDRESSES } from "../contracts"
import { useAccount } from "wagmi"
 
export function TokenBalance() {
  const { address } = useAccount()
 
  const { data, isPending, isError, refetch } = useReadContract({
    address: TOKEN_ADDRESSES[1],
    abi: erc20Abi,
    functionName: "balanceOf",
    args: address ? [address] : undefined,
    query: { enabled: !!address },
  })
 
  if (!address) return <p>Connect wallet dulu</p>
  if (isPending) return <p>Membaca saldo...</p>
  if (isError) return <p>Gagal membaca saldo</p>
 
  return (
    <div>
      <p>Saldo: {formatEther(data ?? 0n)}</p>
      <button onClick={() => refetch()}>Refresh</button>
    </div>
  )
}

Poin penting:

  • query.enabled mencegah pemanggilan sebelum ada address — read tanpa argumen valid akan error.
  • data adalah bigint (satuannya wei) — selalu formatEther/formatUnits sebelum ditampilkan.
  • refetch untuk refresh manual saat user menuntut data terbaru.

Write: useWriteContract

Write mengirim transaksi. Alurnya tiga tahap: kirim → tunggu receipt → tangani hasil.

src/components/Transfer.tsx
import { useState } from "react"
import { useWriteContract, useWaitForTransactionReceipt } from "wagmi"
import { parseEther } from "viem"
import { erc20Abi } from "viem"
import { TOKEN_ADDRESSES } from "../contracts"
 
export function Transfer() {
  const [to, setTo] = useState("")
  const [amount, setAmount] = useState("")
 
  const { writeContract, data: hash, isPending } = useWriteContract()
  const { isConfirming, isConfirmed } = useWaitForTransactionReceipt({ hash })
 
  return (
    <div>
      <input value={to} onChange={(e) => setTo(e.target.value)} placeholder="0x..." />
      <input value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="0.01" />
      <button
        disabled={isPending}
        onClick={() =>
          writeContract({
            address: TOKEN_ADDRESSES[1],
            abi: erc20Abi,
            functionName: "transfer",
            args: [to as `0x${string}`, parseEther(amount)],
          })
        }
      >
        {isPending ? "Menunggu wallet..." : "Transfer"}
      </button>
      {hash && <p>Tx: {hash}</p>}
      {isConfirming && <p>Menunggu konfirmasi...</p>}
      {isConfirmed && <p>Transfer berhasil!</p>}
    </div>
  )
}

Alur yang benar:

  1. writeContract memicu prompt MetaMask; data berisi transaction hash.
  2. useWaitForTransactionReceipt({ hash }) memantau hash sampai terkonfirmasi.
  3. UI berpindah status: isPending (menunggu tanda tangan) → isConfirming (di mempool) → isConfirmed.

isPending di hook useWriteContract berbeda dari useWaitForTransactionReceipt — jangan tertukar.

Pola Approve-then-Swap

Hampir semua interaksi token butuh dua langkah: user approve kontrak untuk menghabiskan token kalian, lalu kontrak transferFrom. Contoh untuk swap di DEX:

100%

Frontend menangani ini sebagai dua transaksi berurutan — umumnya tombol kedua baru aktif setelah approve terkonfirmasi. Beberapa DApp memakai approve dengan maxUint256 agar tidak perlu approve ulang, dengan trade-off keamanan yang kita bahas di episode 16.

Estimasi Gas

Sebelum transaksi, tampilkan estimasi gas agar user tidak kaget:

Estimasi gas dengan viem
const gasEstimate = await publicClient.estimateContractGas({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: "transfer",
  args: [to, parseEther(amount)],
  account: address,
})

Saat estimasi gagal dengan estimateGasExecutionError, biasanya ini tanda masalah nyata (saldonya kurang, allowance belum ada, atau argumen salah) — tangkap error ini untuk menampilkan pesan yang bisa dimengerti user.

Warning

Jangan pernah membiarkan error transaksi mentah tampil di UI ("Execution reverted: ..."). Wrap dalam blok try/catch dan terjemahkan ke bahasa manusia: saldo kurang, allowance belum approve, gas terlalu rendah, atau network sibuk. Error mentah adalah tanda DApp amatir.

Ringkasan Konsep

Rangkuman yang sudah kalian pelajari di episode 11:

  • Read = useReadContract — gratis, tanpa gas; Write = useWriteContract — berbayar, butuh signature.
  • Alur write: kirim (hash) → useWaitForTransactionReceipt → status confirmed.
  • Approve-then-swap adalah pola universal interaksi token.
  • Estimasi gas menangkap error lebih awal — jangan biarkan user menandatangani transaksi yang pasti gagal.

Penutup

Inti yang harus dibawa pulang:

  • Satu sumber kebenaran: hash transaksi + status receipt — UI harus mengikuti keduanya.
  • bigint di mana pun — format hanya saat ditampilkan.
  • Error handling transaksi adalah pembeda DApp profesional.

Di episode 12 selanjutnya, kita membahas events & real-time UIuseWatchContractEvent untuk mendengarkan event seperti Transfer, perbedaan poll dan subscription, refresh saldo setelah transaksi, serta optimistic updates agar UI terasa instan. Sampai jumpa di episode 12!

Belajar Web3 - Interaksi Contract: Read & Write | Belajar Web3