Solidity 101 - Part 3: Menguasai Token ERC-20
Halo! Selamat datang kembali di seri Solidity 101. Setelah sebelumnya kita belajar dasar-dasar Smart Contract, sekarang saatnya kita masuk ke materi yang paling seru dan sering dibicarakan di dunia blockchain: Token.
Panduan ini dibuat khusus agar mudah dimengerti oleh pemula, dengan bahasa yang santai dan langkah-langkah yang praktis. Yuk, kita mulai!
Chapter 0: Persiapan Lingkungan Kerja
Sebelum kita mulai menulis kode, kita perlu menyiapkan “bengkel” kerja kita dulu. Kita masih akan menggunakan Remix IDE, sebuah teks editor berbasis web yang sangat powerful untuk membuat Smart Contract.
Langkah-langkah Persiapan:
- Buka browser kamu dan kunjungi remix.ethereum.org .
- Lihat panel di sebelah kiri (File Explorers), lalu klik kanan pada folder
contracts. - Pilih New File dan beri nama file tersebut:
KuponBazar.sol.
Nah, sekarang kamu sudah siap untuk mulai membuat token pertamamu!
Chapter 1: Apa Itu Token Sebenarnya?
Kita sering mendengar istilah “Token” dan “Koin”, tapi sebenarnya apa sih bedanya? Apakah Bitcoin itu token? Apakah Ethereum itu token? Mari kita luruskan konsep dasarnya.
1. Koin (Native Currency) vs. Token
Bayangkan blockchain (seperti Ethereum, Base, atau Manta) itu adalah sebuah Negara Digital.
- Koin (Native Currency): Ini adalah mata uang resmi negara tersebut (contoh: ETH di Ethereum/Base). Fungsinya vital: untuk membayar pajak jalan atau biaya layanan yang kita sebut Gas Fee. Tanpa Koin ini, kamu cuma bisa “nonton”, tapi gak bisa transaksi.
- Token: Di dalam negara ini, kamu bebas mendirikan “wahana permainan” atau “toko” sendiri. Kamu bisa mencetak “kupon” khusus untuk tokomu. Nah, kupon buatanmu inilah yang disebut Token. Token ini “menumpang hidup” di infrastruktur negara tersebut.
Ingat Rumus Ini:
- Mau kirim Token? Wajib punya Koin (ETH) buat bayar kurirnya (Miner).
- Mau simpan data di blockchain? Wajib punya Koin (ETH) buat bayar biaya simpan.
2. Apakah Project Wajib Punya Token?
Jawabannya: TIDAK SELALU.
Banyak pemula terjebak bikin token padahal gak butuh.
- Jika aplikasimu cuma buat catat ijazah atau data absensi: Gak butuh token. Cukup bayar gas fee pakai ETH.
- Kapan kamu butuh token?
- Sebagai Mata Uang Game (beli item pedang, skin).
- Sebagai Tiket Masuk (Token Gated: cuma yang punya 100 Token boleh masuk grup VIP).
- Sebagai Hak Suara (Governance) (punya token = boleh ikut voting keputusan).
- Sebagai Saham Digital (bagi hasil keuntungan).
3. Cara Kerjanya: Transfer Koin vs. Transfer Token (PENTING!)
Ini konsep teknis yang 90% pemula salah paham.
- Transfer Koin (ETH): Uangnya benar-benar berpindah dari dompet A ke dompet B di level mesin blockchain.
- Transfer Token (ERC-20): Sebenarnya… Token itu TIDAK PERNAH keluar dari dompetmu! Bahkan, token itu tidak pernah berpindah tempat.
Lho, kok bisa? Token itu sebenarnya diam saja di dalam rumahnya, yaitu Smart Contract. Smart Contract itu isinya cuma Buku Catatan (Ledger).
Saat kamu klik “Kirim Token” di wallet, yang terjadi adalah:
- Kamu kirim pesan ke Smart Contract (si Robot Pencatat).
- Isi pesannya: “Halo Robot, tolong coret angka 10 di samping namaku, dan tulis angka 10 di samping nama Budi.”
- Robot melakukan pencatatan itu. Selesai.
💡 Visualisasi: Dompet Metamask kamu itu cuma “Kacamata” untuk melihat catatan saldo kamu di Smart Contract tersebut. Uangnya gak ada di Metamask, tapi di kontrak.
Chapter 2: Mengenal ERC, Nilai Token, & Kegunaannya
Apa itu ERC?
ERC adalah singkatan dari Ethereum Request for Comments. Gampangnya, ini adalah “Standar Nasional”-nya Ethereum. Supaya token buatanmu bisa terbaca di semua dompet (Metamask) dan bisa dijual di pasar (Uniswap), tokenmu harus mengikuti aturan standar ini.
Apa itu ERC-20?
ERC-20 adalah standar paling populer untuk Fungible Token.
- Fungible artinya: Saling Tergantikan. Nilai 1 Token milikmu sama persis dengan 1 Token milik orang lain.
Bagian 1: Dari Mana Token Punya Harga? (Liquidity Logic)
Saat kamu selesai bikin token (Smart Contract jadi), harganya adalah Rp 0. Tokenmu tidak punya nilai sama sekali dan TIDAK BISA DIBELI oleh siapapun.
Kenapa? Karena belum ada “Lawan Tukar”-nya. Bayangkan kamu buka Money Changer tapi brankas kamu kosong.
Lalu, Siapa yang Harus Isi Brankas Pertama Kali? Jawabannya: KAMU SENDIRI (Developer). Ini yang disebut proses Adding Liquidity (Menambah Likuiditas).
- Kamu Cetak Uang (Minting) di Smart Contract kamu. Misal: 1 Juta Token.
- Kamu Siapkan Modal Asli (ETH) dari dompet pribadimu. Misal: 1 ETH.
- Kamu pergi ke DEX (Uniswap) dan setor kedua aset tersebut ke dalam “Kolam” (Pool).
Di situlah harga terbentuk: 1 ETH = 1 Juta Token Kamu. Sekarang, user lain baru bisa datang membawa ETH mereka, memasukkannya ke kolam, dan mengambil Token Kamu.
Mitos vs Fakta: Kemana Larinya Uang User? Banyak yang mengira saat user beli token, uangnya masuk ke dompet Developer. ITU SALAH.
- Fakta: Saat user beli token, ETH mereka masuk ke Kolam Uniswap, BUKAN ke dompet pribadimu.
- Mekanisme: Kolam jadi makin banyak ETH-nya, token makin sedikit. Harga Token jadi NAIK.
- Kapan Developer Untung? ETH yang menumpuk di kolam itu milikmu (penyedia likuiditas). Kamu bisa menariknya kapan saja dengan fitur “Remove Liquidity”.
Bagian 2: Cara User Menggunakan Token (Utility)
Oke, user sudah beli tokenmu di Uniswap. Sekarang token itu buat apa? Bagaimana aplikasi kita tahu kalau user punya token?
Inilah konsep Token Gating (Gerbang Token). Bayangkan Smart Contract aplikasimu itu seperti “Wahana Permainan” dengan “Satpam Digital” di depannya.
Skenario: “Masuk Wahana Wajib Punya 100 Token”
- User Datang: User (Budi) ingin masuk ke fitur Premium.
- Satpam Mengecek: Satpam (Aplikasi) akan menelepon Bank (Smart Contract Token): “Halo Bank, tolong cek saldo si Budi (0x123…) ada berapa?”
- Bank Menjawab: “Budi punya 500 Token.”
- Keputusan Satpam: “Oke, syarat masuk cuma 100. Karena Budi punya 500, silakan masuk!”
PENTING: Di skenario ini, token Budi TIDAK BERKURANG dan TIDAK PINDAH. Satpam cuma “mengintip” saldo sebagai syarat izin masuk. Budi tetap bayar Gas Fee (ETH) untuk biaya pengecekan tersebut.
Bagaimana Kalau Mau Bayar? (TransferFrom) Jika aplikasimu mewajibkan bayar (misal: Beli Item Game seharga 50 Token), maka alurnya sedikit beda:
- Approve: Budi harus klik tombol “Izinkan” dulu di dompetnya. Ini memberi kuasa ke Aplikasimu untuk mengambil 50 Token.
- Execute: Saat Budi beli item, Aplikasimu akan menarik token dari dompet Budi ke dompet Aplikasi secara otomatis.
Jadi, tokenmu bukan sekadar mainan, tapi menjadi Kunci Akses atau Alat Tukar di dalam ekosistem aplikasimu.
Chapter 3: Bedah Fungsi ERC-20 (Building the Blueprint)
Selamat datang di chapter paling penting dalam perjalanan kita!
Bayangkan kamu adalah seorang arsitek. Sebelum membangun 100 rumah di sebuah kompleks perumahan, kamu tidak langsung membangun semuanya satu per satu. Kamu membuat satu denah master yang sempurna dulu—lengkap dengan posisi pintu, jendela, dan instalasi listrik. Setelah denah itu jadi, kamu bisa “copy-paste” dan modifikasi sedikit untuk setiap rumah baru.
Itulah yang akan kita lakukan di chapter ini.
Kontrak KuponBazar.sol yang kita bangun sekarang bukan sekadar satu token. Ini adalah Master Blueprint—sebuah template sempurna yang nantinya akan digunakan oleh Token Factory kita di chapter selanjutnya. Factory itu akan bisa mencetak token-token baru untuk berbagai merchant hanya dengan satu klik, semua berdasarkan blueprint yang kita buat hari ini.
[!WARNING] Jadi, pastikan kamu benar-benar paham setiap baris kode di chapter ini. Jangan cuma copy-paste!
A. Pragma & Versioning
Konsep: Mengapa Ini Penting?
Pernahkah kamu download aplikasi di HP lama dan muncul pesan “Aplikasi ini membutuhkan Android 12 atau lebih baru”? Itu karena aplikasi tersebut menggunakan fitur-fitur yang hanya ada di versi Android tertentu.
Solidity bekerja dengan cara yang sama. Bahasa ini terus berkembang—versi 0.7 berbeda dengan versi 0.8, dan ada fitur-fitur baru serta perbaikan keamanan di setiap update. Baris pragma adalah cara kita memberitahu compiler: “Hei, kode ini ditulis untuk versi sekian. Jangan coba compile pakai versi yang tidak kompatibel!”
Ini seperti stempel tanggal kadaluarsa atau label kompatibilitas pada kode kita.
Contoh Generik
// SPDX-License-Identifier: MIT
// ^ Ini lisensi open-source. Wajib ada di baris pertama.
pragma solidity ^0.8.20;
// ^ "pragma" = aturan untuk compiler
// ^ "solidity" = bahasa yang digunakan
// ^ "^0.8.20" = gunakan versi 0.8.20 atau lebih baru (tapi masih di 0.8.x)
// Contoh lain:
// pragma solidity 0.8.20; → HANYA versi 0.8.20, tidak boleh lain
// pragma solidity >=0.8.0; → Versi 0.8.0 ke atas (termasuk 0.9, 1.0, dst)
// pragma solidity ^0.8.0; → Versi 0.8.0 sampai sebelum 0.9.0Simbol ^ (caret) artinya: “Boleh versi ini atau lebih baru, tapi jangan sampai major version berubah.” Jadi ^0.8.20 akan menerima 0.8.21, 0.8.22, tapi tidak akan menerima 0.9.0.
🎯 Mission A: Checkpoint Pertama
[!NOTE] Saatnya Praktik! Kerjakan latihan berikut untuk membangun fondasi smart contract tokenmu.
Tugas: Buka file KuponBazar.sol kamu yang masih kosong, lalu lengkapi kode di bawah ini.
Latihan: Pragma & Lisensi
Lengkapi lisensi SPDX dan pragma version untuk Solidity 0.8.20
// [1] Tulis lisensi MIT di baris pertama
// [2] Tulis pragma untuk Solidity versi 0.8.20 ke atas (gunakan ^)
B. Importing Libraries (Mengimpor Perpustakaan)
Konsep: Mengapa Ini Penting?
Bayangkan kamu mau bikin kue ulang tahun. Kamu punya dua pilihan:
- Bikin dari nol: Giling gandum jadi tepung, peras sapi jadi susu, kocok telur ayam yang baru dipetik…
- Pakai bahan jadi: Beli tepung terigu, susu UHT, dan telur di supermarket.
Pilihan mana yang lebih masuk akal? Tentu yang kedua.
Dalam programming, library adalah “bahan jadi” yang sudah dibuat oleh developer lain. OpenZeppelin adalah “supermarket” paling terpercaya di dunia Solidity—mereka menyediakan kode standar ERC-20, ERC-721, dan banyak lagi yang sudah diaudit keamanannya oleh para ahli.
Dengan import, kita “meminjam” kode berkualitas tinggi tanpa harus menulis dari nol.
Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Cara import file dari library eksternal:
// import "lokasi/folder/NamaFile.sol";
// Contoh: Import standar ERC20 dari OpenZeppelin
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// ^ "@openzeppelin/contracts" = nama package OpenZeppelin
// ^ "/token/ERC20/" = lokasi folder
// ^ "ERC20.sol" = file yang berisi kode standar token
// Contoh lain yang mungkin kamu temui:
// import "@openzeppelin/contracts/access/Ownable.sol"; → Fitur ownership
// import "@openzeppelin/contracts/security/Pausable.sol"; → Fitur pause/unpausePath @openzeppelin/contracts/... akan otomatis dikenali oleh Remix atau Hardhat karena OpenZeppelin adalah library yang sudah terintegrasi.
🎯 Mission B: Checkpoint Kedua
[!NOTE] Saatnya Praktik! Tambahkan import library yang diperlukan.
Tugas: Tambahkan import statement di bawah pragma.
Latihan: Import Library
Import file ERC20.sol dari OpenZeppelin
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// [3] Import file ERC20.sol dari OpenZeppelin
// Path-nya: @openzeppelin/contracts/token/ERC20/ERC20.sol
C. Contract Declaration & Inheritance (Pewarisan)
Konsep: Mengapa Ini Penting?
Pernah dengar istilah “anak mewarisi DNA orang tua”? Kalau ayahmu jago matematika, ada kemungkinan kamu juga punya bakat itu tanpa harus belajar dari nol.
Dalam Solidity, inheritance bekerja persis seperti itu. Ketika kontrak kita “mewarisi” dari ERC20, kita otomatis mendapatkan semua kemampuan ERC-20:
- Fungsi
transfer()untuk kirim token - Fungsi
balanceOf()untuk cek saldo - Fungsi
approve()dantransferFrom()untuk izin transfer - Dan puluhan fungsi lainnya…
Semua itu GRATIS—tanpa menulis satu baris kode pun! Kita cuma perlu menambahkan kata kunci is.
Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Misalnya kita punya kontrak standar bernama "BankStandard"
// yang sudah punya fitur deposit, withdraw, cek saldo, dll.
import "./BankStandard.sol";
// Kontrak kita MEWARISI dari BankStandard
contract BankKampworng is BankStandard {
// ^ ^
// nama kontrak keyword inheritance
// kita (artinya: "adalah turunan dari")
// Karena sudah "is BankStandard", kontrak ini otomatis bisa:
// - deposit()
// - withdraw()
// - getBalance()
// Tanpa kita tulis ulang!
}
// Contoh lain dengan multiple inheritance:
// contract SuperToken is ERC20, Ownable, Pausable { }
// ^ Mewarisi dari 3 kontrak sekaligus!Kata kunci is seperti mengatakan: “Kontrak ini adalah turunan dari…”
🎯 Mission C: Checkpoint Ketiga
[!NOTE] Saatnya Praktik! Buat deklarasi kontrak dengan inheritance.
Tugas: Buat deklarasi kontrak yang mewarisi ERC20.
Latihan: Contract Declaration
Lengkapi nama kontrak dan inheritance
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// [4] Buat kontrak bernama "KuponBazar" yang mewarisi dari "ERC20"
// Format: contract [NamaKontrak] is [KontrakYangDiwarisi] { }
contract is {
// Kode selanjutnya akan ditulis di dalam sini
}D. Constructor (Akte Kelahiran Token)
Konsep: Mengapa Ini Penting?
Ketika bayi lahir, hal pertama yang dilakukan adalah membuat akte kelahiran—dokumen yang mencatat nama, tanggal lahir, dan orang tua. Proses ini hanya terjadi SEKALI dan tidak bisa diulang.
constructor adalah akte kelahiran untuk smart contract. Fungsi ini:
- Dijalankan sekali saja, yaitu saat kontrak di-deploy
- Digunakan untuk set nilai awal yang tidak berubah
- Tidak bisa dipanggil lagi setelah kontrak live di blockchain
Untuk token ERC-20, constructor dari OpenZeppelin membutuhkan 2 data wajib: Nama Token dan Simbol (Ticker).
Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract KoinKampworng is ERC20 {
// Constructor = fungsi yang jalan saat deploy
constructor() ERC20("Koin Kampwoeng", "KWNG") {
// ^ ^
// keyword memanggil constructor milik ERC20
// dengan parameter: (nama, simbol)
// Kode inisialisasi lain bisa ditulis di sini
}
}
// Penjelasan sintaks:
// constructor() → fungsi tanpa parameter dari kontrak kita
// ERC20("Nama", "SIMBOL") → memanggil constructor parent class
// { } → body constructor (isinya nanti kita tambah di bagian minting)Perhatikan bahwa ERC20("...", "...") ditulis setelah constructor() tapi sebelum kurung kurawal {. Ini adalah cara Solidity memanggil constructor dari parent class.
🎯 Mission D: Checkpoint Keempat
[!NOTE] Saatnya Praktik! Buat constructor untuk tokenmu.
Tugas: Buat constructor untuk token “Kupon Bazar” dengan simbol “KPN”.
Latihan: Constructor
Lengkapi constructor dengan keyword, nama token, dan simbol yang benar
contract KuponBazar is ERC20 {
// [5] Buat constructor yang memanggil ERC20 dengan:
// - Nama token: "Kupon Bazar"
// - Simbol: "KPN"
() ERC20(, ) {
// Nanti kita isi dengan minting
}
}E. Minting (Mencetak Supply Token)
Konsep: Mengapa Ini Penting?
Token yang sudah punya nama dan simbol tapi jumlahnya 0 itu seperti apa? Seperti bank yang sudah punya gedung megah, logo keren, dan nama resmi… tapi tidak punya uang sama sekali. Percuma!
Minting adalah proses “mencetak uang” dalam dunia crypto. Sama seperti Bank Indonesia yang mencetak Rupiah, kita sebagai creator token berhak mencetak supply awal.
OpenZeppelin menyediakan fungsi internal _mint(alamat, jumlah) yang:
- Parameter 1: Alamat tujuan (siapa yang menerima token baru)
- Parameter 2: Jumlah (berapa banyak yang dicetak)
⚠️ Konsep Kritis: Decimals
Ini bagian yang sering bikin bingung pemula!
Solidity tidak mengenal angka desimal (pecahan). Tidak ada 1.5 atau 0.001. Semua angka harus bulat (integer).
Lalu bagaimana token bisa punya pecahan? Solusinya: pura-pura.
Standar ERC-20 menggunakan 18 decimal places. Artinya:
| Yang kamu lihat di wallet | Yang tersimpan di blockchain |
|---|---|
| 1 Token | 1.000.000.000.000.000.000 (1018) |
| 0.5 Token | 500.000.000.000.000.000 |
| 1000 Token | 1.000.000.000.000.000.000.000 (1021) |
Cara mudah menulisnya: gunakan 10**18 (10 pangkat 18).
1 token = 1 * 10**18
100 token = 100 * 10**18
1000 token = 1000 * 10**18Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract KoinKampworng is ERC20 {
constructor() ERC20("Koin Kampwoeng", "KWNG") {
// _mint(alamatTujuan, jumlahDenganDecimals)
_mint(msg.sender, 500 * 10**18);
// ^ ^
// msg.sender = alamat yang men-deploy kontrak ini
// 500 * 10**18 = 500 token (dengan 18 decimal)
// Setelah deploy:
// - Total supply = 500 KWNG
// - Semua dimiliki oleh deployer (msg.sender)
}
}
// Variasi jumlah:
// _mint(msg.sender, 1 * 10**18); → 1 token
// _mint(msg.sender, 1000000 * 10**18); → 1 juta token
// _mint(msg.sender, 21000000 * 10**18); → 21 juta token (seperti Bitcoin)msg.sender adalah variabel global di Solidity yang berisi alamat wallet yang memanggil fungsi tersebut. Dalam konteks constructor, msg.sender adalah alamat yang men-deploy kontrak.
🎯 Mission E: Checkpoint Terakhir
[!NOTE] Saatnya Praktik! Cetak supply awal tokenmu.
Tugas: Cetak 1000 token “Kupon Bazar” ke alamat deployer.
Latihan: Minting Token
Lengkapi fungsi _mint dengan parameter yang benar
contract KuponBazar is ERC20 {
constructor() ERC20("Kupon Bazar", "KPN") {
// [6] Panggil fungsi _mint dengan:
// - Parameter 1: alamat deployer (siapa yang men-deploy?)
// - Parameter 2: 1000 token (jangan lupa decimals!)
_mint(, * );
}
}🏁 Final Check: Verifikasi Blueprint-mu
Sebelum lanjut ke chapter berikutnya, pastikan file KuponBazar.sol kamu sudah memiliki semua komponen ini:
- Lisensi SPDX di baris pertama
- Pragma solidity ^0.8.20
- Import ERC20.sol dari OpenZeppelin
- Contract declaration dengan inheritance (is ERC20)
- Constructor dengan nama “Kupon Bazar” dan simbol “KPN”
- Minting 1000 token ke msg.sender
Kalau semua checklist sudah ✓, selamat! Kamu sudah punya Master Blueprint yang siap digunakan oleh Token Factory di chapter selanjutnya. 🎉
[!IMPORTANT] Catatan Penting: Jangan langsung lihat solusi atau copy-paste dari internet. Coba kerjakan sendiri dulu dengan mengacu pada Contoh Generik di setiap bagian. Struggle itu bagian dari belajar—dan itulah yang akan membedakan kamu dari developer yang terjebak di “tutorial hell”!
Chapter 4: Fitur-Fitur Advanced ERC-20
Sekarang blueprint dasar kita sudah jadi. Tapi token yang baik butuh fitur-fitur tambahan—seperti kemampuan membakar token, mengontrol siapa yang boleh melakukan apa, dan bahkan “membekukan” transfer sementara.
Di bagian ini, kita akan bedah fitur-fitur advanced yang akan membuat Kupon Bazar kita lebih powerful dan aman!
[!WARNING] Peringatan: Fitur-fitur ini akan mengubah cara kerja tokenmu secara fundamental. Pastikan kamu mengikuti setiap langkah dengan teliti.
A. Burning (Membakar Token)
Konsep: Mengapa Token Perlu Dibakar?
Pernahkah kamu dengar istilah “bakar uang”? Di dunia nyata itu bodoh. Tapi di dunia crypto, burning adalah strategi yang sering dipakai untuk:
- Mengurangi supply → Membuat token yang tersisa lebih langka (dan potensial lebih berharga)
- Menghapus token yang tidak digunakan → Membersihkan token yang “nyangkut”
- Mekanisme deflasi → Beberapa protokol membakar sebagian fee transaksi
Burning pada dasarnya adalah mengirim token ke alamat yang tidak bisa diakses siapapun (alamat 0x0), sehingga token tersebut hilang selamanya dari peredaran.
Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
// ^
// Extension khusus untuk fitur burn
contract KoinBakar is ERC20, ERC20Burnable {
// ^
// Tambahkan inheritance kedua
constructor() ERC20("Koin Bakar", "BURN") {
_mint(msg.sender, 10000 * 10**18);
}
// Dengan ERC20Burnable, kontrak otomatis punya:
//
// burn(uint256 amount)
// → Membakar token MILIK SENDIRI (msg.sender)
// → Contoh: burn(100 * 10**18) = bakar 100 token saya
//
// burnFrom(address account, uint256 amount)
// → Membakar token MILIK ORANG LAIN (butuh approval dulu)
// → Contoh: burnFrom(alamatAlice, 50 * 10**18)
}
// Di belakang layar, burn() memanggil fungsi internal:
// _burn(address account, uint256 amount)
// Yang mengurangi balance DAN totalSupplyERC20Burnable adalah extension yang meng-expose fungsi _burn() internal agar bisa dipanggil publik.
🎯 Mission A: Tambahkan Fitur Burn
[!NOTE] Saatnya Praktik! Tambahkan fitur burning ke kontrak tokenmu.
Tugas: Modifikasi kontrak KuponBazar agar bisa burning token.
Latihan: Fitur Burn
Tambahkan import dan inheritance untuk ERC20Burnable
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// [1] Tambahkan import untuk ERC20Burnable
// Path: @openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol
import "";
// [2] Tambahkan inheritance ERC20Burnable setelah ERC20
contract KuponBazar is ERC20, {
constructor() ERC20("Kupon Bazar", "KPN") {
_mint(msg.sender, 1000 * 10**18);
}
}B. Ownable (Kontrol Kepemilikan)
Konsep: Siapa Bos-nya?
Setiap perusahaan punya CEO—satu orang yang punya wewenang tertinggi untuk membuat keputusan besar. Di smart contract, konsep ini disebut Ownership.
Ownable dari OpenZeppelin memberikan satu akun (biasanya deployer) status sebagai owner. Owner bisa melakukan hal-hal sensitif seperti:
- Minting token baru
- Pause/unpause kontrak
- Mengubah parameter penting
Ini seperti memberikan “kunci master” ke satu orang.
Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// ^
// Modul untuk access control
contract TokenBos is ERC20, Ownable {
// Constructor Ownable butuh parameter: siapa owner awal
constructor()
ERC20("Token Bos", "BOS")
Ownable(msg.sender) // ← Set deployer sebagai owner
{
_mint(msg.sender, 1000 * 10**18);
}
// Fungsi ini HANYA bisa dipanggil oleh owner
function mintEksklusif(address to, uint256 amount) public onlyOwner {
// ^^^^^^^^^
// Modifier dari Ownable
_mint(to, amount);
}
// Tanpa onlyOwner, siapapun bisa panggil fungsi ini
function mintBebas(address to, uint256 amount) public {
_mint(to, amount); // BAHAYA! Siapapun bisa cetak token!
}
}
// Fungsi bawaan Ownable:
// - owner() → Cek siapa owner saat ini
// - transferOwnership(newOwner) → Pindahkan kepemilikan
// - renounceOwnership() → Lepaskan kepemilikan (owner jadi 0x0)Modifier onlyOwner adalah “penjaga pintu” yang mengecek: “Apakah yang memanggil fungsi ini adalah owner? Kalau bukan, tolak!”
🎯 Mission B: Tambahkan Kontrol Owner
[!NOTE] Saatnya Praktik! Implementasikan kontrol kepemilikan dengan Ownable.
Tugas: Buat fungsi mint yang hanya bisa dipanggil owner.
Latihan: Ownable
Lengkapi import, inheritance, inisialisasi, dan modifier untuk Ownable
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
// [1] Import modul Ownable
import "";
contract KuponBazar is ERC20, ERC20Burnable, {
// ^
// [2] Tambah inheritance
constructor()
ERC20("Kupon Bazar", "KPN")
(msg.sender) // [3] Inisialisasi owner
{
_mint(msg.sender, 1000 * 10**18);
}
// [4] Buat fungsi mint dengan modifier onlyOwner
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}C. Pausable (Tombol Darurat)
Konsep: Circuit Breaker
Bayangkan kamu punya toko online. Suatu hari, sistemmu kena hack. Apa yang kamu lakukan? Matikan dulu sistemnya sampai masalah teratasi!
Pausable adalah tombol darurat untuk smart contract. Ketika diaktifkan, semua transfer token berhenti total. Ini berguna untuk:
- Merespons exploit/hack
- Maintenance darurat
- Situasi force majeure
Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract TokenDarurat is ERC20, ERC20Pausable, Ownable {
constructor()
ERC20("Token Darurat", "SOS")
Ownable(msg.sender)
{
_mint(msg.sender, 1000 * 10**18);
}
// Tombol PAUSE - hanya owner
function pause() public onlyOwner {
_pause(); // Fungsi internal dari Pausable
}
// Tombol UNPAUSE - hanya owner
function unpause() public onlyOwner {
_unpause();
}
// WAJIB: Override _update untuk menghubungkan Pausable dengan ERC20
function _update(address from, address to, uint256 value)
internal
override(ERC20, ERC20Pausable) // Override dari 2 parent
{
super._update(from, to, value);
}
}
// Setelah pause() dipanggil:
// - transfer() → GAGAL
// - transferFrom() → GAGAL
// - Minting → GAGAL (kecuali dikonfigurasi khusus)
//
// Setelah unpause():
// - Semua kembali normalFungsi _update() adalah “hook” yang dipanggil setiap kali ada pergerakan token. ERC20Pausable menambahkan pengecekan di hook ini.
🎯 Mission C: Tambahkan Fitur Pause
[!NOTE] Saatnya Praktik! Tambahkan tombol darurat ke kontrak tokenmu.
Tugas: Buat KuponBazar bisa di-pause oleh owner.
Latihan: Pausable
Lengkapi import, inheritance, modifier, dan override untuk Pausable
// Tambahkan import yang diperlukan
import "@openzeppelin/contracts/token/ERC20/extensions/.sol";
contract KuponBazar is ERC20, ERC20Burnable, Ownable, {
// ... constructor seperti sebelumnya ...
// [1] Buat fungsi pause() dengan modifier onlyOwner
function pause() public {
(); // [2] Panggil fungsi internal pause
}
function unpause() public onlyOwner {
_unpause();
}
// [3] Override _update - wajib ada!
function _update(address from, address to, uint256 value)
internal
override(, )
{
super._update(from, to, value);
}
}D. Fungsi _update (Hook Kustom)
Konsep: Checkpoint di Setiap Transaksi
Bayangkan setiap kali ada transfer uang di bank, sistem melewati sebuah “pos pemeriksaan” yang bisa:
- Mengecek apakah transaksi valid
- Mencatat log tambahan
- Menjalankan logika khusus
Di OpenZeppelin v5, fungsi _update() adalah “pos pemeriksaan” universal yang dipanggil setiap kali ada:
_mint()(from = address(0))_burn()(to = address(0))_transfer()(normal transfer)
Contoh Generik
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TokenDenganFee is ERC20 {
address public treasury;
constructor(address _treasury) ERC20("Token Fee", "FEE") {
treasury = _treasury;
_mint(msg.sender, 10000 * 10**18);
}
// Override _update untuk menambahkan logika kustom
function _update(address from, address to, uint256 value)
internal
override // Wajib ada karena kita override parent
{
// Logika SEBELUM transfer:
// Contoh: Potong 1% fee untuk setiap transfer (bukan mint/burn)
if (from != address(0) && to != address(0)) {
uint256 fee = value / 100; // 1%
uint256 amountAfterFee = value - fee;
// Transfer fee ke treasury
super._update(from, treasury, fee);
// Transfer sisa ke penerima
super._update(from, to, amountAfterFee);
return; // Jangan lanjut ke super._update di bawah
}
// Untuk mint/burn, jalankan normal
super._update(from, to, value);
}
}
// Alur:
// 1. User panggil transfer(Bob, 1000)
// 2. _update(User, Bob, 1000) dipanggil
// 3. Hook kustom: 10 token ke treasury, 990 ke BobOverride _update() adalah cara paling clean untuk menambahkan logika di setiap transaksi tanpa mengubah fungsi transfer() secara langsung.
E. Struktur Final KuponBazar
Setelah menyelesaikan semua checkpoint, struktur kontrakmu seharusnya seperti ini:
- Lisensi + Pragma
- Import: ERC20, ERC20Burnable, ERC20Pausable, Ownable
- Contract declaration dengan 4 inheritance
- Constructor: ERC20(“Kupon Bazar”, “KPN”), Ownable(msg.sender)
- Minting awal: 1000 token
- Fungsi mint() dengan onlyOwner
- Fungsi pause() dan unpause() dengan onlyOwner
- Override _update() untuk Pausable
Blueprint ini siap menjadi template untuk Token Factory di chapter selanjutnya! 🚀
Chapter 5: Kode Lengkap KuponBazar
Kode Final (Full Version)
// SPDX-License-Identifier: MIT
// ↑ Lisensi open-source. Wajib di baris pertama.
pragma solidity ^0.8.20;
// ↑ Versi compiler. ^0.8.20 = versi 0.8.20 ke atas (tapi masih 0.8.x)
// ═══════════════════════════════════════════════════════════════
// IMPORT LIBRARY
// ═══════════════════════════════════════════════════════════════
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// ↑ Standar dasar ERC-20: transfer, balanceOf, approve, dll.
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
// ↑ Extension: burn() dan burnFrom() untuk menghancurkan token
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
// ↑ Extension: _pause() dan _unpause() untuk freeze semua transfer
import "@openzeppelin/contracts/access/Ownable.sol";
// ↑ Access Control: modifier onlyOwner untuk fungsi sensitif
// ═══════════════════════════════════════════════════════════════
// CONTRACT DECLARATION
// ═══════════════════════════════════════════════════════════════
contract KuponBazar is ERC20, ERC20Burnable, ERC20Pausable, Ownable {
// ↑ Multiple inheritance: kontrak ini mewarisi 4 "DNA" sekaligus
//
// Dari ERC20: transfer(), balanceOf(), approve(), totalSupply()
// Dari ERC20Burnable: burn(), burnFrom()
// Dari ERC20Pausable: whenNotPaused modifier (internal)
// Dari Ownable: onlyOwner modifier, owner(), transferOwnership()
// ═══════════════════════════════════════════════════════════
// CONSTRUCTOR (Dijalankan sekali saat deploy)
// ═══════════════════════════════════════════════════════════
constructor()
ERC20("Kupon Bazar", "KPN") // Set nama & simbol token
Ownable(msg.sender) // Set deployer sebagai owner
{
// Minting Supply Awal: 1000 token ke deployer
// 1000 * 10**18 = 1000 token dengan 18 desimal
_mint(msg.sender, 1000 * 10**18);
}
// ═══════════════════════════════════════════════════════════
// FUNGSI PAUSE/UNPAUSE (Emergency Stop)
// ═══════════════════════════════════════════════════════════
/// @notice Membekukan semua transfer token
/// @dev Hanya bisa dipanggil oleh owner
function pause() public onlyOwner {
_pause();
}
/// @notice Mencairkan kembali transfer token
/// @dev Hanya bisa dipanggil oleh owner
function unpause() public onlyOwner {
_unpause();
}
// ═══════════════════════════════════════════════════════════
// FUNGSI MINT TAMBAHAN
// ═══════════════════════════════════════════════════════════
/// @notice Mencetak token baru ke alamat tertentu
/// @param to Alamat penerima token baru
/// @param amount Jumlah token (dalam wei, bukan unit)
/// @dev Hanya bisa dipanggil oleh owner
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
// ═══════════════════════════════════════════════════════════
// OVERRIDE _update (Wajib untuk resolve konflik inheritance)
// ═══════════════════════════════════════════════════════════
/// @dev Hook yang dipanggil setiap ada mint, burn, atau transfer
/// @param from Alamat pengirim (address(0) jika mint)
/// @param to Alamat penerima (address(0) jika burn)
/// @param value Jumlah token yang dipindahkan
function _update(address from, address to, uint256 value)
internal
override(ERC20, ERC20Pausable) // Override dari 2 parent
{
// super._update() akan:
// 1. Cek apakah kontrak sedang paused (dari ERC20Pausable)
// 2. Update balance from dan to (dari ERC20)
// 3. Emit event Transfer
super._update(from, to, value);
}
}Ringkasan Fitur yang Tersedia
| Fungsi | Akses | Deskripsi |
|---|---|---|
transfer(to, amount) | Publik | Kirim token ke alamat lain |
approve(spender, amount) | Publik | Izinkan pihak lain menggunakan tokenmu |
transferFrom(from, to, amount) | Publik | Kirim token atas nama orang lain (butuh approval) |
balanceOf(account) | Publik | Cek saldo token suatu alamat |
totalSupply() | Publik | Cek total token yang beredar |
burn(amount) | Publik | Bakar token milik sendiri |
burnFrom(account, amount) | Publik | Bakar token orang lain (butuh approval) |
mint(to, amount) | Owner Only | Cetak token baru |
pause() | Owner Only | Bekukan semua transfer |
unpause() | Owner Only | Cairkan kembali transfer |
transferOwnership(newOwner) | Owner Only | Pindahkan kepemilikan kontrak |
renounceOwnership() | Owner Only | Lepaskan kepemilikan (permanent!) |