Memahami events di Solidity: mendeklarasikan dan memancarkan event, peran indexed topics dalam pencarian log, membaca event dengan ethers.js dan abi.decode, serta strategi pengindeksan event di DApp menggunakan The Graph

Setelah di episode 7 kalian menguasai inheritance dan interface, pada episode ini kita membahas cara kontrak berbicara ke luar: events dan logging.
Mengapa episode ini penting? Karena kontrak tidak bisa menampilkan UI, mengirim notifikasi, atau menyimpan riwayat tanpa membayar storage. Events adalah satu-satunya saluran komunikasi dari kontrak ke dunia luar — dan juga salah satu alat paling hemat gas untuk menyimpan riwayat transaksi. Frontend, indexer, dan analitik on-chain semuanya dibangun di atas event.
Event adalah log yang disimpan di blockchain ketika fungsi memancarkannya (dengan emit). Berbeda dengan storage, event tidak bisa dibaca oleh kontrak lain dan tidak memengaruhi state — tetapi bisa dibaca oleh siapa pun dari luar chain, dan jauh lebih murah daripada menyimpan data di storage.
event Transfer(address indexed from, address indexed to, uint256 amount);
event Deposit(address indexed user, uint256 amount);Aturan penting:
emit kapan saja dalam fungsi.indexed.Parameter yang ditandai indexed menjadi topic — bagian log yang diindeks dan bisa dicari. Maksimal 3 topic per event (topic 0 adalah signature event-nya).
event Transfer(address indexed from, address indexed to, uint256 amount);Dengan from dan to sebagai topic, frontend bisa memfilter semua Transfer dari satu alamat tanpa memindai seluruh log — ini kunci efisiensi aplikasi berbasis event.
| Tipe data | Perilaku |
|---|---|
indexed (max 3) | Menjadi topic, bisa difilter cepat |
| Non-indexed | Di-encode dalam data log, tidak bisa difilter |
Perhatikan: tipe reference seperti string dan bytes yang indexed disimpan sebagai hash keccak256-nya, bukan nilainya — gunakan hanya jika memang perlu filter.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;
contract Vault {
event Deposit(address indexed user, uint256 amount, uint256 balance);
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value, balances[msg.sender]);
}
}Perhatikan urutan checks-effects-interactions yang akan kita dalami di episode 15: state diubah dulu, baru event di-emit. Karena event tidak bisa revert bersama transaksi, emit setelah state aman.
Di frontend (ethers.js), event bisa dibaca dengan dua cara: query log lama atau subscribe ke event baru.
const contract = new ethers.Contract(address, abi, signer);
// 1. Query log lama (sejak block X)
const filter = contract.filters.Deposit(userAddress);
const logs = await contract.queryFilter(filter, 0, "latest");
// 2. Subscribe ke event baru
contract.on("Deposit", (user, amount, balance) => {
console.log(`${user} deposit ${ethers.formatEther(amount)} ETH`);
});contract.filters.Deposit(userAddress) memfilter hanya event milik alamat tertentu — ini memanfaatkan indexed topic di atas. Data non-indexed (amount, balance) di-decode otomatis oleh ethers.js dari ABI.
Untuk penguraian manual (misalnya di backend tanpa ethers), abi.decode bekerja pada data log:
// Di kontrak, memanggil log kontrak lain
bytes memory data = log.data;
(uint256 amount, uint256 balance) = abi.decode(data, (uint256, uint256));Tip
Ethers.js dan viem sudah menangani decode event dari ABI secara otomatis — kalian jarang butuh abi.decode manual kecuali membangun indexer sendiri. Yang penting dipahami adalah struktur: topics untuk filter, data untuk nilai.
Satu DApp nyata biasanya menggabungkan keduanya:
contract.on untuk update halaman tanpa refresh.queryFilter untuk mengambil log lama.Untuk skala besar, memindai log langsung tidak cukup. Di sinilah The Graph masuk: subgraph mendengarkan event kontrak, memprosesnya, dan menyimpan data terstruktur di database yang bisa di-query cepat.
query {
deposits(first: 5, orderBy: amount, orderDirection: desc) {
user
amount
balance
}
}Pola pengembangan indexer umum: mapping subgraph menangkap event Deposit, menyimpan user, amount, balance, lalu menyediakannya via GraphQL. Ini cara standar DApp DeFi menyajikan riwayat — kita kembali ke The Graph di episode 13.
Warning
Event tidak bisa diandalkan sebagai penyimpanan bisnis yang "tunggal": ia tidak bisa dibaca kontrak lain dan bisa (secara teknis) di-spam. Untuk state yang harus dibaca on-chain, tetap gunakan storage; simpan riwayat di event.
Inti yang harus dibawa pulang:
indexed (topic) yang bisa difilter; sisanya tersimpan di data.contract.on (realtime) dan queryFilter (histori).Di episode 9 selanjutnya kita akan membahas toolchain: Foundry & Hardhat — perbedaan kedua framework, forge init/build/test, forge script untuk deploy, cast untuk interaksi RPC, dan ekosistem plugin Hardhat dengan hardhat-toolbox. Sampai jumpa di episode 9!