Belajar Solidity Basic - Step by Step
Dalam tutorial ini, kita akan belajar Solidity step-by-step dari yang paling dasar menggunakan konteks mahasiswa. Setiap konsep akan dijelaskan satu per satu dengan contoh yang mudah dipahami.
Setup Awal
1. Setup Remix IDE
Remix adalah tempat untuk menulis dan menjalankan smart contract secara online.
Langkah-langkah:
- Buka https://remix.ethereum.org
- Kenali bagian-bagian Remix:
- Kiri: File Explorer - tempat file kontrak
- Tengah: Editor - tempat menulis kode
- Bawah: Terminal - tempat melihat hasil
- Kanan: Compiler dan Deploy
2. Setup MetaMask
MetaMask adalah dompet digital untuk berinteraksi dengan blockchain.
Langkah-langkah:
- Download MetaMask dari metamask.io
- Buat wallet baru (simpan seed phrase dengan aman!)
3. Setup Ethereum Sepolia Testnet (WAJIB!)
🔧 Setup Ethereum Sepolia Testnet (WAJIB!):
Ikuti langkah-langkah berikut untuk setup testnet:
Step 1: Tambah Sepolia ke MetaMask
Sepolia biasanya sudah tersedia di MetaMask. Jika belum, tambahkan melalui Chainlist:
🔗 Chainlist - Add Sepolia
Step 2: Claim Sepolia ETH dari Google Cloud Faucet
Claim Sepolia ETH gratis untuk biaya gas:
🚰 Google Sepolia Faucet
Step 3: Faucet Alternatif (Alchemy)
Jika Google Faucet tidak tersedia, gunakan Alchemy Faucet:
🚰 Alchemy Sepolia Faucet
- Sepolia ETH tidak memiliki nilai nyata, hanya untuk testing
- Pastikan MetaMask menunjukkan network "Sepolia" (Chain ID: 11155111)
- Jika faucet limit tercapai, coba lagi keesokan hari
4. Hubungkan Remix dengan MetaMask
- Di Remix, klik tab "Deploy & Run Transactions"
- Di bagian Environment, pilih "Injected Provider - MetaMask"
- Pastikan MetaMask terhubung ke Sepolia Test Network
Sekarang Anda sudah siap untuk belajar Solidity!
4.1 Solidity 101: Tipe Data Dasar
Kita akan belajar satu tipe data per waktu menggunakan contoh data mahasiswa.
1. String (Teks)
Apa itu: Menyimpan teks seperti "Budi Santoso" atau "Teknik Informatika"
Mengapa penting: Digunakan untuk nama, alamat, deskripsi, dan semua data berbentuk teks
Buat file LearnString.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnString {
// Variabel string untuk menyimpan nama mahasiswa
string public studentName;
// Constructor mengatur nilai awal
constructor() {
studentName = "Budi Santoso";
}
// Fungsi untuk mengubah nama
function changeName(string memory _newName) public {
studentName = _newName;
}
}
Penjelasan:
string public studentName- membuat variabel string yang bisa dibaca orang lainconstructor()- fungsi yang jalan sekali saat kontrak dibuatstring memory _newName- parameter input bertipe string (temporary di memory)memory- kata kunci untuk data temporary (tidak tersimpan permanent)
Coba:
- Deploy → Klik
studentName→ Lihat "Budi Santoso" - Ketik "Ani Wijaya" → Klik
changeName - Klik
studentName→ Sekarang "Ani Wijaya"!
2. Number (uint256)
Apa itu: Menyimpan bilangan bulat positif dari 0 hingga sangat besar (2^256-1)
Mengapa penting: Untuk ID, NIM, nilai, jumlah, score, dan semua data numerik
Buat LearnNumber.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnNumber {
// Angka untuk data mahasiswa
uint256 public studentId;
uint256 public credits;
constructor() {
studentId = 2101001;
credits = 0;
}
function changeStudentId(uint256 _newId) public {
studentId = _newId;
}
function addCredits() public {
credits = credits + 3;
// Bisa juga ditulis: credits += 3;
}
}
Penjelasan:
uint256- unsigned integer 256-bit (0 sampai 2^256-1)studentId = 2101001- mengisi nilai ke variabelcredits += 3- cara singkat untuk credits = credits + 3
Coba:
- Deploy → Klik
credits→ Lihat 0 - Klik
addCredits4 kali - Klik
credits→ Sekarang 12 (4 x 3)!
3. Boolean (Benar/Salah)
Apa itu: Hanya dua nilai - true atau false
Mengapa penting: Untuk status aktif/tidak, lulus/tidak lulus, dan semua kondisi ya/tidak
Buat LearnBoolean.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnBoolean {
bool public isActive;
bool public hasGraduated;
constructor() {
isActive = true;
hasGraduated = false;
}
// Fungsi untuk mengubah status
function changeStatus(bool _status) public {
isActive = _status;
}
// Fungsi untuk lulus
function graduate() public {
hasGraduated = true;
}
}
Penjelasan:
bool- tipe data yang hanya bisatrueataufalseisActive = true- mengisi nilai boolean- Berguna untuk menyimpan status ya/tidak
Coba:
- Deploy → Klik
hasGraduated→ Lihat false - Klik
graduate - Klik
hasGraduated→ Sekarang true!
4. Address (Alamat Wallet)
Apa itu: Menyimpan alamat wallet Ethereum (20 bytes, seperti 0x742d35Cc...)
Mengapa penting: Untuk identifikasi kepemilikan, pengirim transaksi, dan tujuan pembayaran
Buat LearnAddress.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnAddress {
address public admin;
address public student;
constructor() {
admin = msg.sender; // msg.sender = alamat wallet Anda
}
function setStudent(address _student) public {
student = _student;
}
}
Poin Penting:
address- menyimpan alamat wallet (0x742d35Cc...)msg.sender- alamat yang memanggil fungsi- Digunakan untuk kepemilikan, pembayaran, kontrol akses
Coba:
- Deploy → Klik
admin→ Lihat alamat wallet Anda! - Salin alamat lain → Paste di
setStudent - Klik
student→ Lihat alamat tersebut
5. Tantangan: Buat StudentData Sendiri!
Apa yang dipelajari: Menggabungkan semua tipe data dalam satu contract
Tujuan: Tulis contract StudentData.sol SENDIRI tanpa copy-paste!
Spesifikasi Contract:
State Variables yang dibutuhkan:
studentName(tipe: string, public) → nama mahasiswastudentId(tipe: uint256, public) → NIMisActive(tipe: bool, public) → status aktifwallet(tipe: address, public) → alamat walletregisteredTime(tipe: uint256, public) → waktu pendaftaran
Constructor:
- Set
studentName= "Budi Santoso" - Set
studentId= 2101001 - Set
isActive= true - Set
wallet= alamat yang deploy (gunakanmsg.sender) - Set
registeredTime= waktu sekarang (gunakanblock.timestamp)
Fungsi yang dibutuhkan:
updateCredits()- Tipe: public - Aksi: Menambah state variablecredits(uint256) sebesar 3getAge()- Tipe: public view - Return: uint256 - Aksi: Hitung lama terdaftar (waktu sekarang - waktu daftar)
Mulai Coding:
Buat file baru StudentData.sol di Remix dan mulai dari template ini:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract StudentData {
// TODO 1: Deklarasikan 6 state variables
// Hint: string public studentName;
// Hint: uint256 public studentId;
// ... tulis 4 lainnya!
// TODO 2: Buat constructor
constructor() {
// Set nilai awal untuk semua variables
// Hint: studentName = "Budi Santoso";
// Hint: wallet = msg.sender;
}
// TODO 3: Buat fungsi updateCredits()
// Hint: function updateCredits() public { ... }
// TODO 4: Buat fungsi getAge()
// Hint: function getAge() public view returns (uint256) { ... }
// Hint: return block.timestamp - registeredTime;
}
Checklist Verifikasi:
Setelah selesai, cek apakah contract Anda memenuhi ini:
- Ada 6 state variables (string, uint256, bool, address, uint256, uint256)
- Semua variables adalah public
- Constructor mengatur semua nilai awal
- Fungsi
updateCredits()menambah credits sebesar 3 - Fungsi
getAge()adalah view dan return uint256 - Fungsi
getAge()menghitung selisih waktu - Contract dapat di-compile tanpa error
Test Contract Anda:
1. Deploy
- Klik "Deploy" di Remix
- Lihat contract muncul di "Deployed Contracts"
2. Test Variables
- Klik
studentName→ Harus tampil "Budi Santoso" - Klik
studentId→ Harus tampil 2101001 - Klik
isActive→ Harus tampil true - Klik
wallet→ Harus tampil alamat wallet Anda - Klik
registeredTime→ Harus tampil angka (Unix timestamp)
3. Test Fungsi updateCredits()
- Klik
updateCredits→ Popup MetaMask muncul - Confirm transaksi
- Klik
credits→ Harus tampil 3
4. Test Fungsi getAge()
- Klik
getAge→ Langsung tampil angka (GRATIS!) - Tunggu 1 menit
- Klik
getAgelagi → Angka bertambah ~60 detik!
Penjelasan Konsep:
Variabel Khusus Solidity:
msg.sender- alamat wallet yang memanggil fungsiblock.timestamp- waktu blok saat ini (Unix timestamp dalam detik)
Modifier Fungsi:
public- siapa saja dapat memanggil fungsiview- hanya membaca, tidak mengubah state → GRATIS!returns (uint256)- fungsi mengembalikan angka
Cara Kerja:
- Constructor berjalan sekali saat deploy
- State variables tersimpan permanen di blockchain
- Fungsi view GRATIS dipanggil dari luar (no gas!)
- Fungsi yang mengubah state butuh gas (popup MetaMask)
Tips Debugging:
Error Umum:
"ParserError: Expected ';' but got '}'"
→ Lupa titik koma di akhir statement
"TypeError: Member 'sender' not found"
→ Salah ketik: msg.sender (huruf kecil semua!)
"DeclarationError: Undeclared identifier"
→ Variabel belum dideklarasikan atau typo nama
"TypeError: Different number of arguments"
→ Cek parameter fungsi, apakah sudah sesuai?
Jika Stuck:
- Cek kembali 4 contract sebelumnya (String, Number, Boolean, Address)
- Lihat pattern yang sama
- Kombinasikan pattern tersebut
- Jangan menyerah! Ini proses belajar
Setelah Berhasil:
Selamat! Anda sudah menguasai:
- 4 tipe data dasar (string, uint256, bool, address)
- State variables
- Constructor
- Public functions
- View functions (read-only)
- Global variables (msg.sender, block.timestamp)
Selanjutnya: Pelajari Struct & Enum untuk data yang lebih kompleks!
6. Global Variables (Variabel Bawaan Solidity)
Apa itu: Variabel khusus yang sudah disediakan oleh Solidity dan dapat diakses dari mana saja dalam smart contract tanpa perlu mendeklarasikannya.
Mengapa penting: Memberikan informasi penting tentang transaksi, block, dan konteks eksekusi yang diperlukan untuk logic smart contract.
Kategori Global Variables
1. Block Properties
| Variable | Tipe | Deskripsi |
|---|---|---|
block.timestamp | uint256 | Waktu block saat ini (Unix timestamp dalam detik) |
block.number | uint256 | Nomor block saat ini |
block.chainid | uint256 | Chain ID dari blockchain (Sepolia = 11155111) |
block.coinbase | address payable | Alamat miner/validator yang membuat block |
block.difficulty | uint256 | Difficulty block saat ini |
block.gaslimit | uint256 | Gas limit block saat ini |
block.basefee | uint256 | Base fee block saat ini (EIP-1559) |
blockhash(uint256) | bytes32 | Hash dari block tertentu (hanya 256 block terakhir) |
2. Transaction Properties (msg)
| Variable | Tipe | Deskripsi |
|---|---|---|
msg.sender | address | Alamat yang memanggil fungsi saat ini |
msg.value | uint256 | Jumlah wei (ETH) yang dikirim bersama transaksi |
msg.data | bytes | Data lengkap dari calldata |
msg.sig | bytes4 | 4 byte pertama dari calldata (function selector) |
3. Transaction Properties (tx)
| Variable | Tipe | Deskripsi |
|---|---|---|
tx.origin | address | Alamat yang memulai transaksi (EOA asli) |
tx.gasprice | uint256 | Harga gas dari transaksi |
4. Contract Properties
| Variable | Tipe | Deskripsi |
|---|---|---|
this | address | Alamat contract saat ini |
address(this).balance | uint256 | Saldo ETH contract saat ini |
Contoh Penggunaan Global Variables
Buat LearnGlobalVariables.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnGlobalVariables {
// ============================================
// BLOCK PROPERTIES
// ============================================
// Mendapatkan waktu block saat ini
function getCurrentTimestamp() public view returns (uint256) {
return block.timestamp;
}
// Mendapatkan nomor block saat ini
function getCurrentBlockNumber() public view returns (uint256) {
return block.number;
}
// Mendapatkan chain ID (Sepolia = 11155111)
function getChainId() public view returns (uint256) {
return block.chainid;
}
// Mendapatkan alamat validator/miner
function getBlockCoinbase() public view returns (address) {
return block.coinbase;
}
// Mendapatkan gas limit block
function getBlockGasLimit() public view returns (uint256) {
return block.gaslimit;
}
// Mendapatkan hash dari block sebelumnya
function getPreviousBlockHash() public view returns (bytes32) {
return blockhash(block.number - 1);
}
// ============================================
// MESSAGE (MSG) PROPERTIES
// ============================================
// Mendapatkan alamat pemanggil fungsi
function getMsgSender() public view returns (address) {
return msg.sender;
}
// Mendapatkan jumlah ETH yang dikirim (dalam wei)
function getMsgValue() public payable returns (uint256) {
return msg.value;
}
// Mendapatkan calldata lengkap
function getMsgData() public pure returns (bytes calldata) {
return msg.data;
}
// Mendapatkan function selector (4 bytes pertama)
function getMsgSig() public pure returns (bytes4) {
return msg.sig;
}
// ============================================
// TRANSACTION (TX) PROPERTIES
// ============================================
// Mendapatkan alamat EOA yang memulai transaksi
function getTxOrigin() public view returns (address) {
return tx.origin;
}
// Mendapatkan harga gas transaksi
function getTxGasPrice() public view returns (uint256) {
return tx.gasprice;
}
// ============================================
// CONTRACT PROPERTIES
// ============================================
// Mendapatkan alamat contract ini
function getContractAddress() public view returns (address) {
return address(this);
}
// Mendapatkan saldo contract (dalam wei)
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
// ============================================
// PRACTICAL EXAMPLES
// ============================================
address public owner;
uint256 public deployTime;
uint256 public deployBlock;
constructor() {
owner = msg.sender; // Simpan alamat deployer
deployTime = block.timestamp; // Simpan waktu deploy
deployBlock = block.number; // Simpan block deploy
}
// Contoh: Hanya owner yang bisa memanggil
function onlyOwnerCanCall() public view returns (string memory) {
require(msg.sender == owner, "Bukan owner!");
return "Anda adalah owner!";
}
// Contoh: Hitung berapa lama contract sudah di-deploy
function getContractAge() public view returns (uint256) {
return block.timestamp - deployTime;
}
// Contoh: Hitung berapa block sejak deploy
function getBlocksSinceDeploy() public view returns (uint256) {
return block.number - deployBlock;
}
// Contoh: Terima ETH dan catat pengirim
event Received(address indexed sender, uint256 amount, uint256 timestamp);
function deposit() public payable {
emit Received(msg.sender, msg.value, block.timestamp);
}
// Contoh: Validasi chain ID (pastikan di Sepolia)
function validateChain() public view returns (bool) {
return block.chainid == 11155111; // Sepolia Chain ID
}
}
Coba di Remix:
- Deploy contract di Sepolia
- Test Block Properties:
- Klik
getCurrentTimestamp→ Lihat Unix timestamp - Klik
getCurrentBlockNumber→ Lihat nomor block - Klik
getChainId→ Harus 11155111 (Sepolia)
- Klik
- Test Msg Properties:
- Klik
getMsgSender→ Lihat alamat wallet Anda - Masukkan VALUE 0.001 ETH → Klik
getMsgValue→ Lihat jumlah wei
- Klik
- Test Tx Properties:
- Klik
getTxOrigin→ Sama dengan msg.sender (jika langsung dari wallet)
- Klik
- Test Contract Properties:
- Klik
getContractAddress→ Alamat contract - Klik
getContractBalance→ Saldo contract (0 jika belum ada deposit)
- Klik
- Test Practical Examples:
- Klik
onlyOwnerCanCall→ "Anda adalah owner!" - Klik
getContractAge→ Detik sejak deploy - Masukkan VALUE → Klik
deposit→ Lihat event di logs
- Klik
Perbedaan Penting: msg.sender vs tx.origin
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract ContractA {
function callContractB(address _contractB) public view returns (address, address) {
return ContractB(_contractB).whoCalledMe();
}
}
contract ContractB {
function whoCalledMe() public view returns (address sender, address origin) {
sender = msg.sender; // Alamat yang LANGSUNG memanggil (bisa contract)
origin = tx.origin; // Alamat EOA yang MEMULAI transaksi
}
}
Skenario:
- User (EOA: 0xUser) → ContractA → ContractB
- Di ContractB:
msg.sender= alamat ContractAtx.origin= alamat User (0xUser)
JANGAN gunakan tx.origin untuk autentikasi! Rentan terhadap phishing attack. Selalu gunakan msg.sender untuk cek kepemilikan.
Ringkasan Global Variables
| Variable | Kegunaan Utama |
|---|---|
msg.sender | Cek siapa yang memanggil, access control |
msg.value | Cek jumlah ETH yang dikirim |
block.timestamp | Logic berbasis waktu, deadline |
block.number | Tracking block, randomness (tidak aman) |
block.chainid | Validasi network yang benar |
address(this) | Referensi ke contract sendiri |
address(this).balance | Cek saldo contract |
4.2 Solidity 102: Struct & Enum
Sekarang kita akan belajar cara mengelompokkan data dengan Struct dan Enum.
1. Enum (Angka Bernama)
Apa itu: Tipe data dengan nilai yang sudah ditentukan sebelumnya
Mengapa penting: Membuat kode lebih mudah dibaca dan mencegah nilai yang tidak valid
Buat LearnEnum.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnEnum {
enum StudentStatus {
NotRegistered, // 0
Active, // 1
OnLeave, // 2
Graduated, // 3
Dropped // 4
}
StudentStatus public currentStatus;
constructor() {
currentStatus = StudentStatus.NotRegistered;
}
function register() public {
currentStatus = StudentStatus.Active;
}
function takeLeave() public {
if (currentStatus == StudentStatus.Active) {
currentStatus = StudentStatus.OnLeave;
}
}
function graduate() public {
if (currentStatus == StudentStatus.Active) {
currentStatus = StudentStatus.Graduated;
}
}
}
Penjelasan:
enum StudentStatus { ... }- mendefinisikan tipe data dengan nilai fixedNotRegistered= 0,Active= 1, dst (otomatis increment)currentStatus = StudentStatus.Active- mengisi nilai enum- Lebih aman dan mudah dibaca daripada menggunakan angka langsung
Coba:
- Deploy → Klik
currentStatus→ Lihat 0 (NotRegistered) - Klik
register→ Status berubah ke 1 (Active) - Klik
graduate→ Status berubah ke 3 (Graduated)
2. Struct (Kelompok Data)
Apa itu: Cara untuk mengelompokkan beberapa variabel menjadi satu tipe data kustom
Mengapa penting: Mengorganisir data yang saling berhubungan, membuat kode lebih rapi
Buat LearnStruct.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnStruct {
enum StudentStatus { NotRegistered, Active, OnLeave, Graduated }
struct Student {
uint256 id;
string name;
address wallet;
StudentStatus status;
uint8 credits;
bool isActive;
}
Student public myStudent;
constructor() {
myStudent = Student({
id: 2101001,
name: "Budi Santoso",
wallet: msg.sender,
status: StudentStatus.Active,
credits: 0,
isActive: true
});
}
function addCredits() public {
myStudent.credits += 3;
}
function changeStatus(StudentStatus _status) public {
myStudent.status = _status;
}
}
Penjelasan:
struct Student { ... }- membuat tipe kustom baru yang mengelompokkan data- Seperti membuat template: setiap Student memiliki id, name, wallet, dll
Student public myStudent- membuat variabel bertipe StudentmyStudent = Student({ ... })- mengisi struct dengan datamyStudent.credits += 3- akses dan ubah field tertentu
Coba:
- Deploy
- Klik
myStudent→ Lihat semua data tergabung - Klik
addCredits→ Credits bertambah 3 - Klik
myStudentlagi → Lihat perubahan!
4.3 Solidity 103: Mapping & Array
Pelajari cara mengelola banyak mahasiswa.
1. Mapping (Kamus)
Apa itu: Seperti kamus - memetakan kunci ke nilai (studentId → data)
Mengapa penting: Mengasosiasikan data dengan identifier unik, pencarian cepat berdasarkan kunci
Buat LearnMapping.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnMapping {
// Mapping: studentId => name
mapping(uint256 => string) public studentName;
// Mapping: studentId => credits
mapping(uint256 => uint256) public studentCredits;
function addStudent(uint256 _id, string memory _name) public {
studentName[_id] = _name;
studentCredits[_id] = 0;
}
function addCredits(uint256 _id, uint256 _amount) public {
studentCredits[_id] += _amount;
}
}
Penjelasan:
mapping(uint256 => string)- seperti kamus, map studentId (key) ke name (value)mapping(uint256 => uint256)- map studentId ke creditsstudentName[_id] = _name- set nama untuk student ID tertentupublicpada mapping otomatis membuat fungsi getter
Coba:
- Deploy
- Ketik 2101001, "Budi" → Klik
addStudent - Ketik 2101002, "Ani" → Klik
addStudent - Ketik 2101001 di
studentName→ Lihat "Budi" - Ketik 2101002 di
studentName→ Lihat "Ani" - Sekarang Anda punya 2 mahasiswa!
2. Array (Daftar)
Apa itu: Daftar berurutan yang dapat bertambah ukurannya
Mengapa penting: Menyimpan koleksi yang dapat diiterasi, mendapatkan semua item sekaligus
Buat LearnArray.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnArray {
// Array untuk menyimpan student ID
uint256[] public allStudentIds;
// Tambah mahasiswa
function addStudent(uint256 _id) public {
allStudentIds.push(_id);
}
// Dapatkan total mahasiswa
function getTotalStudents() public view returns (uint256) {
return allStudentIds.length;
}
// Dapatkan semua student ID
function getAllStudents() public view returns (uint256[] memory) {
return allStudentIds;
}
// Dapatkan student tertentu by index
function getStudentByIndex(uint256 _index) public view returns (uint256) {
return allStudentIds[_index];
}
}
Penjelasan:
uint256[]- array dinamis yang dapat bertambah ukurannyaallStudentIds.push(_id)- menambahkan elemen ke akhir arrayallStudentIds.length- mengembalikan jumlah elemenallStudentIds[0]- akses elemen pertama (array dimulai dari indeks 0!)returns (uint256[] memory)- mengembalikan seluruh array
Coba:
- Deploy
- Tambah student dengan ID: 2101001, 2101002, 2101003
- Klik
getTotalStudents→ Lihat 3 - Klik
getAllStudents→ Lihat [2101001, 2101002, 2101003] - Ketik 0 di
getStudentByIndex→ Lihat 2101001 (index pertama!)
3. Mapping + Struct (Banyak Mahasiswa)
Apa itu: Menggabungkan mapping dengan struct untuk menyimpan banyak item kompleks
Mengapa penting: Mengelola banyak item dengan data kompleks, pola smart contract dunia nyata
Buat MultipleStudents.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract MultipleStudents {
enum StudentStatus { NotRegistered, Active, OnLeave, Graduated }
struct Student {
uint256 id;
string name;
address wallet;
StudentStatus status;
uint8 credits;
bool exists;
}
// Mapping untuk menyimpan mahasiswa
mapping(uint256 => Student) public students;
// Counter
uint256 public studentCounter;
// Tambah mahasiswa baru
function addStudent(string memory _name) public returns (uint256) {
studentCounter++;
students[studentCounter] = Student({
id: studentCounter,
name: _name,
wallet: msg.sender,
status: StudentStatus.Active,
credits: 0,
exists: true
});
return studentCounter;
}
// Tambah credits
function addCredits(uint256 _id, uint8 _amount) public {
students[_id].credits += _amount;
}
// Dapatkan info mahasiswa
function getStudent(uint256 _id) public view returns (Student memory) {
return students[_id];
}
// Check apakah mahasiswa exist
function studentExists(uint256 _id) public view returns (bool) {
return students[_id].exists;
}
}
Penjelasan:
mapping(uint256 => Student) public students- map studentId ke Student structstudentCounter++- increment counter (membuat ID unik)students[studentCounter] = Student({ ... })- simpan mahasiswa baru di mappingreturns (uint256)- fungsi mengembalikan ID mahasiswa barureturns (Student memory)- fungsi mengembalikan copy Student struct- Menggabungkan mapping + struct untuk kelola banyak mahasiswa!
Coba:
- Deploy
- Ketik "Budi" → Klik
addStudent→ Return studentId=1 - Ketik "Ani" → Klik
addStudent→ Return studentId=2 - Ketik 1 di
getStudent→ Lihat data mahasiswa #1 - Ketik 2 di
getStudent→ Lihat data mahasiswa #2 - Ketik 1, 6 di
addCredits→ Tambah 6 credits untuk mahasiswa #1 - Ketik 1 di
getStudent→ Lihat credits bertambah!
4.4 Solidity 104: Modifier & Event
Tambahkan keamanan dan komunikasi.
1. Require (Validasi)
Apa itu: Penjaga keamanan yang memeriksa kondisi sebelum menjalankan kode
Mengapa penting: Mencegah tindakan tidak sah, validasi input, penting untuk keamanan
Buat LearnRequire.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnRequire {
mapping(uint256 => address) public studentOwner;
mapping(uint256 => uint256) public studentCredits;
function registerStudent(uint256 _id) public {
studentOwner[_id] = msg.sender;
studentCredits[_id] = 0;
}
function addCredits(uint256 _id, uint256 _amount) public {
// Cek apakah caller adalah pemilik student
require(studentOwner[_id] == msg.sender, "Bukan student Anda!");
// Cek amount valid
require(_amount > 0, "Amount harus lebih dari 0");
studentCredits[_id] += _amount;
}
}
Penjelasan:
require(condition, "error message")- cek apakah kondisi benar- Jika kondisi FALSE → transaksi gagal dan tampilkan pesan error
- Jika kondisi TRUE → kode lanjut ke baris berikutnya
studentOwner[_id] == msg.sender- cek apakah caller adalah pemilik- Digunakan untuk validasi dan security checks
Coba:
- Deploy
- Ketik 2101001 → Klik
registerStudent - Ketik 2101001, 3 → Klik
addCredits→ BERHASIL (Anda pemiliknya) - Ganti ke akun lain di MetaMask
- Ketik 2101001, 3 → Coba
addCredits→ GAGAL "Bukan student Anda!" - Ketik 2101001, 0 → Coba
addCredits→ GAGAL "Amount harus lebih dari 0"
2. Modifier (Pemeriksaan Reusable)
Apa itu: Pembungkus validasi yang dapat digunakan kembali untuk banyak fungsi
Mengapa penting: Hindari pengulangan require, kode lebih bersih, prinsip DRY
Buat LearnModifier.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnModifier {
address public admin;
mapping(uint256 => address) public studentOwner;
mapping(uint256 => uint256) public studentCredits;
uint256 public adminActionCount;
constructor() {
admin = msg.sender;
}
// Modifier: hanya admin yang bisa call
modifier onlyAdmin() {
require(msg.sender == admin, "Hanya admin!");
_;
}
// Modifier: harus pemilik student
modifier onlyStudentOwner(uint256 _id) {
require(studentOwner[_id] == msg.sender, "Bukan student Anda!");
_;
}
function registerStudent(uint256 _id) public {
studentOwner[_id] = msg.sender;
studentCredits[_id] = 0;
}
// Hanya admin yang bisa call ini
function adminFunction() public onlyAdmin {
adminActionCount++;
}
// Hanya pemilik student yang bisa add credits
function addCredits(uint256 _id, uint256 _amount) public onlyStudentOwner(_id) {
require(_amount > 0, "Amount harus lebih dari 0");
studentCredits[_id] += _amount;
}
}
Penjelasan:
modifier onlyAdmin() { ... }- membuat check yang reusable_- placeholder di mana kode fungsi akan berjalanfunction adminFunction() public onlyAdmin- apply modifier- Modifier berjalan SEBELUM fungsi (check kondisi dulu)
adminActionCount++- tambah counter (hanya admin bisa)- Lebih bersih daripada menulis require di setiap fungsi!
- Dapat gunakan beberapa modifier pada satu fungsi
Coba:
- Deploy
- Klik
adminActionCount→ Lihat 0 - Klik
adminFunction→ BERHASIL (Anda admin) - Klik
adminActionCount→ Sekarang 1! - Ganti akun → Coba
adminFunction→ GAGAL "Hanya admin!" - Ketik 2101001 →
registerStudent→ BERHASIL - Ketik 2101001, 3 →
addCredits→ BERHASIL - Ganti akun → Ketik 2101001, 3 →
addCredits→ GAGAL "Bukan student Anda!"
3. Event (Komunikasi)
Apa itu: Menyiarkan log tentang apa yang terjadi di contract (disimpan di blockchain)
Mengapa penting: Frontend listen real-time updates, track history, debugging tool
Buat LearnEvents.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnEvents {
// Deklarasi event
event StudentRegistered(address indexed owner, uint256 indexed studentId, string name);
event CreditsAdded(uint256 indexed studentId, uint256 credits, uint256 totalCredits);
mapping(uint256 => address) public studentOwner;
mapping(uint256 => string) public studentName;
mapping(uint256 => uint256) public studentCredits;
uint256 public studentCounter;
function registerStudent(string memory _name) public {
studentCounter++;
studentOwner[studentCounter] = msg.sender;
studentName[studentCounter] = _name;
studentCredits[studentCounter] = 0;
// Emit event
emit StudentRegistered(msg.sender, studentCounter, _name);
}
function addCredits(uint256 _id, uint256 _amount) public {
require(studentOwner[_id] == msg.sender, "Bukan student Anda!");
require(_amount > 0, "Amount harus lebih dari 0");
studentCredits[_id] += _amount;
// Emit event
emit CreditsAdded(_id, _amount, studentCredits[_id]);
}
}
Penjelasan:
event StudentRegistered(...)- deklarasi event (data apa yang dicatat)indexed- membuat parameter searchable (max 3 indexed parameters)emit StudentRegistered(msg.sender, studentCounter, _name)- trigger event- Event disimpan di blockchain tapi TIDAK menghabiskan gas untuk dibaca
- Frontend dapat listen event secara real-time
- Digunakan untuk: logging, notifications, track history
Coba:
- Deploy
- Ketik "Budi" → Klik
registerStudent - Lihat transaksi di console Remix
- Klik "logs" → Lihat event StudentRegistered!
- Ketik 1, 3 → Klik
addCredits - Lihat event CreditsAdded di logs!
4.5 Solidity 105: Payable & Time-Based
Akhirnya, tambahkan uang (ETH) dan logic berbasis waktu!
1. Fungsi Payable
Apa itu: Keyword yang memungkinkan fungsi menerima ETH (msg.value)
Mengapa penting: Menerima pembayaran, donasi, hadiah. Tanpa ini, kirim ETH akan gagal
Buat LearnPayable.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnPayable {
uint256 public studentCounter;
mapping(uint256 => address) public studentOwner;
// Fungsi payable dapat menerima ETH
function registerStudent() public payable returns (uint256) {
require(msg.value >= 0.001 ether, "Perlu 0.001 ETH untuk registrasi");
studentCounter++;
studentOwner[studentCounter] = msg.sender;
return studentCounter;
}
// Cek saldo contract
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
Penjelasan:
payable- keyword yang allow fungsi terima ETHmsg.value- jumlah ETH yang dikirim dengan transaksi (dalam wei)0.001 ether- compiler convert ke wei (1 ether = 10^18 wei)require(msg.value >= 0.001 ether)- check minimum paymentaddress(this).balance- saldo ETH contract- Tanpa
payable, kirim ETH akan gagal!
Coba:
- Deploy
- Di Remix, cari field "VALUE" (di atas tombol Deploy)
- Masukkan 1 dan pilih milliether (= 0.001 ETH)
- Klik
registerStudent→ Konfirmasi di MetaMask - Klik
getBalance→ Lihat 0.001 ETH di contract!
2. Mengirim ETH
Apa itu: Mengirim ETH dari contract ke alamat menggunakan .call{value: amount}("")
Mengapa penting: Membayar hadiah, refund, withdrawal. Selalu check success!
Buat LearnSendETH.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnSendETH {
address public admin;
mapping(address => uint256) public scholarships;
constructor() {
admin = msg.sender;
}
// Terima ETH untuk scholarship fund
function deposit() public payable {}
// Kirim scholarship ke student
function sendScholarship(address _student, uint256 _amount) public {
require(msg.sender == admin, "Hanya admin");
require(address(this).balance >= _amount, "Saldo tidak cukup");
// Kirim ETH
(bool success, ) = _student.call{value: _amount}("");
require(success, "Transfer gagal");
scholarships[_student] += _amount;
}
// Cek saldo
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
Penjelasan:
function deposit() public payable {}- terima ETH tanpa kode_student.call{value: _amount}("")- kirim ETH ke alamat(bool success, ) = ...- tangkap apakah transfer berhasilrequire(success, "Transfer gagal")- revert jika kirim gagal.calladalah cara modern dan aman untuk kirim ETH- Cara lama:
.transfer()dan.send()TIDAK recommended
Coba:
- Deploy
- Kirim ETH menggunakan
depositdengan field VALUE (misal: 10 milliether) - Klik
getBalance→ Lihat deposit Anda - Ketik alamat student dan amount (misal: 1000000000000000 = 0.001 ETH)
- Klik
sendScholarship→ ETH terkirim!
3. Time-Based Logic
Apa itu: Menggunakan block timestamp untuk logic berbasis waktu
Mengapa penting: Untuk deadline, cooldown, durasi event, aging system
Buat LearnTime.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract LearnTime {
mapping(uint256 => uint256) public studentRegisteredTime;
mapping(uint256 => uint256) public lastSubmission;
uint256 public constant ASSIGNMENT_COOLDOWN = 1 days;
function registerStudent(uint256 _id) public {
studentRegisteredTime[_id] = block.timestamp;
}
// Check berapa lama sudah terdaftar (dalam detik)
function getStudentAge(uint256 _id) public view returns (uint256) {
require(studentRegisteredTime[_id] > 0, "Student belum terdaftar");
return block.timestamp - studentRegisteredTime[_id];
}
// Submit assignment (max 1x per hari)
function submitAssignment(uint256 _id) public {
require(
block.timestamp >= lastSubmission[_id] + ASSIGNMENT_COOLDOWN,
"Harus tunggu 1 hari"
);
lastSubmission[_id] = block.timestamp;
}
// Check kapan bisa submit lagi
function timeUntilNextSubmission(uint256 _id) public view returns (uint256) {
if (lastSubmission[_id] == 0) {
return 0; // Bisa submit sekarang
}
uint256 nextSubmissionTime = lastSubmission[_id] + ASSIGNMENT_COOLDOWN;
if (block.timestamp >= nextSubmissionTime) {
return 0; // Bisa submit sekarang
}
return nextSubmissionTime - block.timestamp;
}
}
Penjelasan:
block.timestamp- waktu block saat ini (Unix timestamp dalam detik)1 days,1 hours,1 minutes- time unitsconstant- nilai tidak bisa diubah setelah di-setstudentRegisteredTime[_id] = block.timestamp- catat waktu registrasiblock.timestamp - studentRegisteredTime[_id]- hitung umurblock.timestamp >= lastSubmission[_id] + ASSIGNMENT_COOLDOWN- cek cooldown
Coba:
- Deploy
- Ketik 2101001 →
registerStudent - Ketik 2101001 →
getStudentAge→ Lihat 0 (baru terdaftar) - Tunggu 1 menit →
getStudentAge→ Lihat ~60 detik - Ketik 2101001 →
submitAssignment→ BERHASIL - Langsung coba
submitAssignmentlagi → GAGAL "Harus tunggu 1 hari" - Ketik 2101001 →
timeUntilNextSubmission→ Lihat sisa waktu (dalam detik)
Final Challenge: CrowdFund - Decentralized Crowdfunding Platform
Tentang Challenge Ini
CrowdFund: Decentralized Crowdfunding Platform
Bangun platform crowdfunding terdesentralisasi di Ethereum! Challenge ini menggabungkan semua konsep yang telah dipelajari untuk menciptakan platform penggalangan dana yang transparan dan trustless.
Deskripsi Challenge
Latar Belakang: Platform crowdfunding tradisional seperti Kickstarter memiliki beberapa masalah: biaya perantara tinggi, kurang transparan, dan dana bisa disalahgunakan. Dengan blockchain, kita bisa membuat platform crowdfunding yang:
- Transparan - Semua kontribusi dan pencairan tercatat on-chain
- Trustless - Dana hanya bisa dicairkan jika target tercapai
- Refundable - Kontributor otomatis bisa refund jika campaign gagal
- No Middleman - Tidak ada perantara yang mengambil fee berlebihan
Misi Anda:
Buat smart contract CrowdFund.sol yang memungkinkan:
- Creator membuat campaign crowdfunding dengan target dan deadline
- Siapa saja bisa berkontribusi ETH ke campaign
- Jika target tercapai sebelum deadline, creator bisa claim dana
- Jika target TIDAK tercapai, kontributor bisa refund
- Tracking kontribusi dan status campaign secara real-time
Spesifikasi Teknis
Data Structures
// Status campaign
enum CampaignStatus {
Active, // 0: Sedang berjalan, menerima kontribusi
Successful, // 1: Target tercapai, dana bisa di-claim
Failed, // 2: Deadline lewat, target tidak tercapai
Claimed // 3: Dana sudah di-claim oleh creator
}
// Data campaign
struct Campaign {
uint256 campaignId;
address creator;
string title;
string description;
uint256 goalAmount; // Target dana yang dibutuhkan
uint256 currentAmount; // Dana yang sudah terkumpul
uint256 deadline; // Batas waktu campaign
uint256 createdAt; // Waktu pembuatan
CampaignStatus status;
uint256 contributorCount; // Jumlah kontributor unik
}
State Variables yang Dibutuhkan
uint256 public campaignCounter;
uint256 public constant MIN_GOAL = 0.01 ether;
uint256 public constant MAX_DURATION = 90 days;
uint256 public constant MIN_DURATION = 1 days;
uint256 public constant MIN_CONTRIBUTION = 0.001 ether;
mapping(uint256 => Campaign) public campaigns;
mapping(uint256 => mapping(address => uint256)) public contributions;
mapping(uint256 => mapping(address => bool)) public hasContributed;
mapping(address => uint256[]) public creatorCampaigns;
Events yang Dibutuhkan
event CampaignCreated(uint256 indexed campaignId, address indexed creator, string title, uint256 goalAmount, uint256 deadline);
event ContributionMade(uint256 indexed campaignId, address indexed contributor, uint256 amount, uint256 totalRaised);
event CampaignSuccessful(uint256 indexed campaignId, uint256 totalRaised);
event FundsClaimed(uint256 indexed campaignId, address indexed creator, uint256 amount);
event RefundIssued(uint256 indexed campaignId, address indexed contributor, uint256 amount);
event CampaignFailed(uint256 indexed campaignId, uint256 totalRaised, uint256 goalAmount);
Functions yang Harus Dibuat
| Function | Akses | Deskripsi |
|---|---|---|
createCampaign(string _title, string _description, uint256 _goalAmount, uint256 _durationDays) | Public | Buat campaign baru |
contribute(uint256 _campaignId) | Public, Payable | Kontribusi ETH ke campaign |
claimFunds(uint256 _campaignId) | Creator Only | Claim dana jika target tercapai |
refund(uint256 _campaignId) | Contributor Only | Refund jika campaign gagal |
checkCampaign(uint256 _campaignId) | Public | Update status campaign (cek deadline) |
getCampaignDetails(uint256 _campaignId) | View | Lihat detail campaign |
getMyContribution(uint256 _campaignId) | View | Lihat kontribusi saya di campaign |
getMyCampaigns() | View | Lihat semua campaign yang saya buat |
getTimeRemaining(uint256 _campaignId) | View | Lihat sisa waktu campaign |
getContractBalance() | View | Lihat total saldo contract |
Modifiers yang Dibutuhkan
modifier campaignExists(uint256 _campaignId)
modifier onlyCreator(uint256 _campaignId)
modifier onlyContributor(uint256 _campaignId)
modifier isActive(uint256 _campaignId)
Template Starter Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
/// @title CrowdFund - Decentralized Crowdfunding Platform
/// @author [Nama Anda]
/// @notice Platform crowdfunding terdesentralisasi di Ethereum
/// @dev Challenge Final Ethereum Co-Learning Camp
contract CrowdFund {
// ============================================
// ENUMS & STRUCTS
// ============================================
enum CampaignStatus {
Active,
Successful,
Failed,
Claimed
}
struct Campaign {
uint256 campaignId;
address creator;
string title;
string description;
uint256 goalAmount;
uint256 currentAmount;
uint256 deadline;
uint256 createdAt;
CampaignStatus status;
uint256 contributorCount;
}
// ============================================
// STATE VARIABLES
// ============================================
// TODO: Deklarasikan state variables
// Hint: campaignCounter, constants, mappings
// ============================================
// EVENTS
// ============================================
// TODO: Deklarasikan semua events
// ============================================
// MODIFIERS
// ============================================
// TODO: Buat modifiers (campaignExists, onlyCreator, dll)
// ============================================
// MAIN FUNCTIONS
// ============================================
/// @notice Buat campaign crowdfunding baru
/// @param _title Judul campaign
/// @param _description Deskripsi campaign
/// @param _goalAmount Target dana (dalam wei)
/// @param _durationDays Durasi campaign dalam hari
function createCampaign(
string memory _title,
string memory _description,
uint256 _goalAmount,
uint256 _durationDays
) public {
// TODO: Implementasi
// 1. Validasi goalAmount >= MIN_GOAL
// 2. Validasi durasi (MIN_DURATION <= duration <= MAX_DURATION)
// 3. Increment campaignCounter
// 4. Hitung deadline = block.timestamp + (_durationDays * 1 days)
// 5. Buat Campaign struct baru
// 6. Simpan di mapping
// 7. Tambahkan campaignId ke creatorCampaigns
// 8. Emit event
}
/// @notice Kontribusi ETH ke campaign
/// @param _campaignId ID campaign
function contribute(uint256 _campaignId) public payable {
// TODO: Implementasi
// 1. Validasi campaign masih Active
// 2. Validasi belum lewat deadline
// 3. Validasi msg.value >= MIN_CONTRIBUTION
// 4. Validasi creator tidak bisa kontribusi ke campaign sendiri
// 5. Update contributions mapping
// 6. Update currentAmount
// 7. Track contributor unik (hasContributed)
// 8. Jika currentAmount >= goalAmount, update status ke Successful
// 9. Emit event
}
/// @notice Creator claim dana setelah campaign sukses
/// @param _campaignId ID campaign
function claimFunds(uint256 _campaignId) public {
// TODO: Implementasi
// 1. Validasi caller adalah creator
// 2. Validasi status = Successful
// 3. Update status ke Claimed
// 4. Transfer dana ke creator
// 5. Emit event
}
/// @notice Kontributor refund jika campaign gagal
/// @param _campaignId ID campaign
function refund(uint256 _campaignId) public {
// TODO: Implementasi
// 1. Validasi status = Failed
// 2. Validasi caller punya kontribusi > 0
// 3. Simpan amount, set kontribusi ke 0 (prevent reentrancy)
// 4. Transfer refund ke caller
// 5. Emit event
}
/// @notice Cek dan update status campaign
/// @param _campaignId ID campaign
function checkCampaign(uint256 _campaignId) public {
// TODO: Implementasi
// Jika deadline lewat dan status masih Active:
// - Jika currentAmount >= goalAmount → Successful
// - Jika currentAmount < goalAmount → Failed
}
// ============================================
// VIEW FUNCTIONS
// ============================================
/// @notice Lihat detail campaign
function getCampaignDetails(uint256 _campaignId) public view returns (Campaign memory) {
// TODO: Implementasi
}
/// @notice Lihat kontribusi saya di campaign
function getMyContribution(uint256 _campaignId) public view returns (uint256) {
// TODO: Implementasi
}
/// @notice Lihat semua campaign yang saya buat
function getMyCampaigns() public view returns (uint256[] memory) {
// TODO: Implementasi
}
/// @notice Lihat sisa waktu campaign
function getTimeRemaining(uint256 _campaignId) public view returns (uint256) {
// TODO: Implementasi
// Jika deadline sudah lewat, return 0
// Jika belum, return deadline - block.timestamp
}
/// @notice Lihat saldo contract
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
}
Cara Submit Challenge
Step 1: Buat Repository GitHub
- Buat repository baru di GitHub dengan nama:
eth-crowdfund-[nama-anda] - Struktur folder:
eth-crowdfund-[nama-anda]/
├── contracts/
│ └── CrowdFund.sol
├── README.md
└── screenshots/
├── deploy.png
├── create-campaign.png
├── contribute.png
├── claim-or-refund.png
└── campaign-details.png
Step 2: Isi README.md
# CrowdFund - Ethereum Co-Learning Camp Challenge
## Author
- Nama: [Nama Lengkap]
- GitHub: [username]
- Wallet: [address yang digunakan untuk deploy]
## Contract Address (Sepolia)
`0x...`
## Features Implemented
- [x] Create Campaign
- [x] Contribute ETH
- [x] Claim Funds (if successful)
- [x] Refund (if failed)
- [x] Check Campaign Status
- [ ] Bonus: [sebutkan jika ada]
## Screenshots
[Sertakan screenshots dari Remix]
## How to Test
1. Deploy contract di Sepolia
2. Create campaign dengan goal dan deadline
3. Contribute ETH dari akun berbeda
4. Jika goal tercapai → Creator claim funds
5. Jika deadline lewat dan goal belum tercapai → Contributor refund
## Lessons Learned
[Tulis apa yang Anda pelajari dari challenge ini]
Step 3: Submit di HackQuest
Detail Submission HackQuest:
- Task Type: Link
- Link: URL GitHub Repository Anda
- Section: Belajar dengan HackQuest
Tips Mengerjakan Challenge
- Mulai dari yang simple - Implementasi
createCampaigndangetCampaignDetailsdulu - Test setiap fungsi - Jangan lanjut sebelum fungsi sebelumnya bekerja
- Gunakan Remix - Deploy di Remix VM dulu sebelum ke Sepolia
- Perhatikan reentrancy - Di fungsi
refund, set kontribusi ke 0 SEBELUM transfer - Baca error message - Error Solidity biasanya informatif
- Cek gas - Pastikan ada cukup Sepolia ETH untuk deploy dan testing
Konsep yang Diuji
Challenge ini menguji pemahaman Anda tentang:
- Tipe data dasar (string, uint256, bool, address)
- Struct & Enum
- Mapping & Array
- Modifier & Access Control
- Events
- Payable Functions
- Time-based Logic
- Global Variables (msg.sender, msg.value, block.timestamp)
- Reentrancy Prevention Pattern
Rewards
Peserta yang berhasil menyelesaikan challenge akan mendapatkan:
- Points di HackQuest Community
- Certificate of Completion dari Ethereum Co-Learning Camp
- Networking dengan komunitas Web3 Indonesia
Tips Belajar Solidity
- Mulai dari yang sederhana - Jangan langsung membuat contract kompleks
- Test setiap fungsi - Pastikan setiap fungsi bekerja sebelum lanjut
- Baca error message - Error message memberikan petunjuk masalah
- Practice, practice, practice - Semakin banyak latihan, semakin paham
- Join komunitas - Bergabung dengan Discord ETHJKT
Langkah Selanjutnya
Setelah menguasai basic Solidity, lanjut ke:
- Hardhat Development - Professional development tools
- Token Standards - ERC-20, ERC-721, ERC-1155
- DeFi Basics - Memahami decentralized finance
- Security Best Practices - Membuat contract yang aman
Smart contract development adalah skill yang butuh banyak practice. Mulai dengan project kecil dan tingkatkan kompleksitasnya secara bertahap.
Ready to build on Ethereum!