Skip to main content

Sesi 2: Smart Contract Modular Architecture & Kampus Token Suite

Informasi Umum Sesi​

Tanggal: Hari 2
Durasi Total: 8 jam (08:00 - 16:30)
Tema Pembelajaran: Arsitektur Modular & Token Standards Implementation

Melanjutkan perjalanan pembangunan "Ekosistem Digital Kampus", sesi ini memfokuskan pada implementasi dompet multi-signature untuk tata kelola BEM yang transparan dan suite token kampus yang komprehensif untuk berbagai use-case di lingkungan universitas.


Jadwal Harian Detail​

WaktuAktivitasTujuanMateri & Fokus Utama
09:15 – 09:45Kuliah 104 & 105 + Tantangan 1 BEM MultiSignatureWalletMemahami dompet multi-sig & quorum M-of-NSecurity pattern, struktur contract, governance BEM
09:45 – 10:00Demo Deploy Hardhat IgnitionMemperlihatkan workflow deploy modernKonfigurasi ignition, env vars, verify
10:00 – 12:00Coding & Unit-Test MultiSig BEMPraktik end-to-end dompet treasurySubmitTx, confirm, execute, revoke, event-driven UX
13:30 – 14:30Kuliah — Smart Contract Modular ArchitectureMemahami ERC-20/721/1155 & inheritanceInterface, library reuse, role-based minting, pause pattern
14:30 – 16:00ERC Suite Challenge (CampusCredit, StudentID, CourseBadge)Implementasi kontrak token kampusOpenZeppelin, mint/burn, pause, access control
16:00 – 16:30Sesi Evaluasi & ReviewDemo, kuis, peer-reviewGas optimisasi, mitigasi risiko, next-steps

Kuliah 104: Error Handling & Code Organization (09:15 - 09:25)​

Error Handling & Reporting​

Error handling dalam Solidity adalah fondasi untuk membangun smart contract yang aman dan reliable. Setiap kondisi abnormal harus ditangani dengan baik untuk melindungi user dan funds.

Tiga Mekanisme Utama​

1. require() - Input Validation & Pre-conditions

// Use Case: Validasi saldo sebelum transfer
function transferFunds(address recipient, uint256 amount) public {
require(recipient != address(0), "Invalid recipient address");
require(balances[msg.sender] >= amount, "Insufficient balance");
require(amount > 0, "Amount must be positive");

balances[msg.sender] -= amount;
balances[recipient] += amount;
}

// Real Scenario: BEM Treasury withdrawal
function withdrawFromTreasury(uint256 amount) public onlyTreasurer {
require(amount <= dailyLimit, "Exceeds daily withdrawal limit");
require(lastWithdrawal + 1 days <= block.timestamp, "Daily limit reached");
// ... withdrawal logic
}

2. revert() - Complex Error Handling

// Use Case: Multi-condition validation dengan custom errors
error InsufficientBalance(uint256 requested, uint256 available);
error UnauthorizedAccess(address caller, string role);

function complexWithdraw(uint256 amount) public {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(amount, balances[msg.sender]);
}

if (!hasRole(TREASURER_ROLE, msg.sender)) {
revert UnauthorizedAccess(msg.sender, "TREASURER");
}

// Process withdrawal
}

3. assert() - Internal Invariants

// Use Case: Memastikan mathematical invariants
function distributeFunds(address[] memory recipients, uint256[] memory amounts) public {
uint256 totalBefore = address(this).balance;

for (uint i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(amounts[i]);
}

// This should NEVER fail - jika fail, ada bug serius
assert(address(this).balance == totalBefore - sumArray(amounts));
}

Libraries - Reusable Code Modules​

Libraries memungkinkan code reuse tanpa deployment berulang, menghemat gas dan meningkatkan maintainability.

Library untuk Financial Calculations​

// Library untuk kalkulasi keuangan kampus
library CampusFinance {
uint256 constant ADMIN_FEE_PERCENTAGE = 5; // 5%
uint256 constant LATE_PAYMENT_PENALTY = 2; // 2% per minggu

function calculateAdminFee(uint256 amount) internal pure returns (uint256) {
return (amount * ADMIN_FEE_PERCENTAGE) / 100;
}

function calculateLatePenalty(uint256 amount, uint256 weeksLate)
internal pure returns (uint256)
{
return (amount * LATE_PAYMENT_PENALTY * weeksLate) / 100;
}

function splitPayment(uint256 total)
internal pure returns (uint256 principal, uint256 fee)
{
fee = calculateAdminFee(total);
principal = total - fee;
}
}

// Penggunaan dalam contract
contract TuitionPayment {
using CampusFinance for uint256;

function payTuition(uint256 amount) public payable {
(uint256 principal, uint256 adminFee) = amount.splitPayment();
// Transfer principal ke university account
// Transfer admin fee ke operational account
}
}

Library untuk Array Operations​

// Library untuk operasi array yang sering digunakan
library ArrayUtils {
function remove(address[] storage arr, address value) internal {
for (uint i = 0; i < arr.length; i++) {
if (arr[i] == value) {
arr[i] = arr[arr.length - 1];
arr.pop();
break;
}
}
}

function contains(address[] memory arr, address value)
internal pure returns (bool)
{
for (uint i = 0; i < arr.length; i++) {
if (arr[i] == value) return true;
}
return false;
}
}

Inheritance - Building Complex Systems​

Inheritance memungkinkan contract membangun di atas functionality yang sudah ada.

Single Inheritance Example​

// Base contract untuk semua kampus contracts
contract KampusBase {
address public universityAdmin;
bool public emergencyStop;

modifier onlyAdmin() {
require(msg.sender == universityAdmin, "Admin only");
_;
}

modifier whenNotPaused() {
require(!emergencyStop, "Contract paused");
_;
}

function pause() external onlyAdmin {
emergencyStop = true;
}
}

// Child contract inherit base functionality
contract StudentRegistry is KampusBase {
mapping(address => bool) public registeredStudents;

function registerStudent(address student)
external
onlyAdmin
whenNotPaused
{
registeredStudents[student] = true;
}
}

Multiple Inheritance & Override​

// Multiple inheritance untuk complex features
contract Timestamped {
uint256 public createdAt;

constructor() {
createdAt = block.timestamp;
}

function age() public view virtual returns (uint256) {
return block.timestamp - createdAt;
}
}

contract Expirable is Timestamped {
uint256 public expiresAt;

constructor(uint256 _duration) {
expiresAt = createdAt + _duration;
}

function isExpired() public view returns (bool) {
return block.timestamp >= expiresAt;
}

function age() public view virtual override returns (uint256) {
if (isExpired()) {
return expiresAt - createdAt;
}
return super.age();
}
}

Interfaces - Contract Communication Standards​

Interfaces mendefinisikan contract API tanpa implementation, enabling modularity.

Real Use Case: Payment Gateway Interface​

// Interface untuk berbagai payment methods di kampus
interface IPaymentGateway {
function processPayment(address payer, uint256 amount) external returns (bool);
function getTransactionStatus(bytes32 txId) external view returns (string memory);
function refund(bytes32 txId, uint256 amount) external returns (bool);
}

// Implementation untuk different payment methods
contract CryptoPayment is IPaymentGateway {
function processPayment(address payer, uint256 amount)
external override returns (bool)
{
// Logic untuk crypto payment
return true;
}

function getTransactionStatus(bytes32 txId)
external view override returns (string memory)
{
// Check on-chain status
return "completed";
}

function refund(bytes32 txId, uint256 amount)
external override returns (bool)
{
// Process crypto refund
return true;
}
}

// Main contract yang use interface
contract TuitionPortal {
IPaymentGateway public paymentGateway;

function setPaymentGateway(address _gateway) external onlyAdmin {
paymentGateway = IPaymentGateway(_gateway);
}

function payTuition() external payable {
require(paymentGateway.processPayment(msg.sender, msg.value), "Payment failed");
// Update student payment record
}
}

Kuliah 105: Special Functions & Low-Level Operations (09:25 - 09:45)​

Special & Built-In Functions​

Solidity menyediakan global variables dan functions untuk interact dengan blockchain.

Message Context Variables​

contract MessageContext {
// Track siapa yang melakukan setiap action
event ActionPerformed(
address indexed performer,
uint256 value,
uint256 timestamp,
string action
);

function demonstrateMessageContext() external payable {
// msg.sender - immediate caller
address caller = msg.sender;

// msg.value - ETH sent with transaction
uint256 ethSent = msg.value;

// msg.data - complete calldata
bytes memory data = msg.data;

// msg.sig - function selector (first 4 bytes)
bytes4 selector = msg.sig;

emit ActionPerformed(caller, ethSent, block.timestamp, "context_demo");
}

// Real use case: Donation tracking
mapping(address => uint256) public donations;

function donate() external payable {
require(msg.value > 0, "Donation must be positive");
donations[msg.sender] += msg.value;

// Special reward untuk large donors
if (msg.value >= 1 ether) {
// Grant special NFT or privileges
}
}
}

Block Information​

contract AcademicDeadlines {
struct Assignment {
string title;
uint256 deadline;
uint256 submittedAt;
}

mapping(uint256 => Assignment) public assignments;

function submitAssignment(uint256 assignmentId) external {
Assignment storage assignment = assignments[assignmentId];

// Check deadline using block.timestamp
require(block.timestamp <= assignment.deadline, "Past deadline");

assignment.submittedAt = block.timestamp;

// Bonus points untuk early submission
uint256 daysEarly = (assignment.deadline - block.timestamp) / 1 days;
if (daysEarly >= 3) {
// Award bonus points
}
}

// Randomness untuk lottery (NOT secure for production)
function pickRandomWinner(address[] memory participants)
external view returns (address)
{
uint256 random = uint256(keccak256(abi.encodePacked(
block.timestamp,
block.difficulty,
block.number,
participants.length
)));

return participants[random % participants.length];
}
}

Transaction Information​

contract SecurityAudit {
// Differentiate between direct calls and contract calls
modifier onlyDirectCall() {
require(tx.origin == msg.sender, "No contract calls allowed");
_;
}

// High-security function - only EOA dapat call
function emergencyWithdraw() external onlyDirectCall {
// Withdraw logic
}

// Gas management
function complexCalculation(uint256[] memory data) external {
uint256 startGas = gasleft();

// Process data...
for (uint i = 0; i < data.length; i++) {
// Check gas setiap 100 iterations
if (i % 100 == 0 && gasleft() < 50000) {
revert("Insufficient gas for safe completion");
}
// Process...
}

uint256 gasUsed = startGas - gasleft();
emit GasReport(gasUsed);
}
}

ABI (Application Binary Interface)​

ABI adalah bridge antara high-level Solidity code dan low-level EVM bytecode.

Understanding ABI Components​

// Contract dengan various function types
contract ABIExample {
// Event - part of ABI
event DataStored(address indexed user, uint256 value);

// State variable dengan getter - ada di ABI
uint256 public storedData;

// External function - ada di ABI
function store(uint256 _data) external {
storedData = _data;
emit DataStored(msg.sender, _data);
}

// External view function - ada di ABI
function retrieve() external view returns (uint256) {
return storedData;
}

// Internal function - TIDAK ada di ABI
function _internalLogic() internal pure returns (uint256) {
return 42;
}
}

/* Generated ABI akan include:
{
"functions": [
{
"name": "store",
"inputs": [{"name": "_data", "type": "uint256"}],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "retrieve",
"inputs": [],
"outputs": [{"type": "uint256"}],
"stateMutability": "view"
}
],
"events": [...],
...
}
*/

Function Selectors​

Function selectors adalah cara EVM identify function mana yang dipanggil.

Computing & Using Function Selectors​

contract SelectorExample {
// Function selector = first 4 bytes of keccak256(signature)

function computeSelector() external pure returns (bytes4) {
// Selector untuk "transfer(address,uint256)"
return bytes4(keccak256("transfer(address,uint256)"));
// Returns: 0xa9059cbb
}

// Manual function routing using selectors
fallback() external payable {
bytes4 selector = msg.sig;

if (selector == bytes4(keccak256("customFunction()"))) {
// Route to custom logic
} else if (selector == 0xa9059cbb) { // transfer selector
// Handle transfer
} else {
revert("Function not found");
}
}

// Real use case: Proxy pattern
contract MinimalProxy {
address immutable implementation;

constructor(address _implementation) {
implementation = _implementation;
}

fallback() external payable {
address impl = implementation;
assembly {
// Copy calldata to memory
calldatacopy(0, 0, calldatasize())

// Delegatecall to implementation
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)

// Copy return data
returndatacopy(0, 0, returndatasize())

// Return or revert
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
}

Low-Level Calls​

Low-level calls memberikan flexibility untuk complex interactions.

Types of Low-Level Calls​

contract LowLevelCalls {
// 1. Regular CALL - execute di context target contract
function regularCall(address target, bytes memory data)
external payable returns (bool, bytes memory)
{
(bool success, bytes memory result) = target.call{value: msg.value}(data);
return (success, result);
}

// 2. STATICCALL - guaranteed no state changes
function readOnlyCall(address target, bytes memory data)
external view returns (bool, bytes memory)
{
(bool success, bytes memory result) = target.staticcall(data);
return (success, result);
}

// 3. DELEGATECALL - execute di context contract ini
function delegateCall(address target, bytes memory data)
external returns (bool, bytes memory)
{
(bool success, bytes memory result) = target.delegatecall(data);
return (success, result);
}
}

Real World Use Cases​

// Multi-call pattern untuk batch operations
contract MultiCall {
struct Call {
address target;
uint256 value;
bytes data;
}

function multiCall(Call[] memory calls)
external payable returns (bytes[] memory results)
{
results = new bytes[](calls.length);

for (uint i = 0; i < calls.length; i++) {
(bool success, bytes memory result) = calls[i].target.call{
value: calls[i].value
}(calls[i].data);

require(success, string(abi.encodePacked("Call ", i, " failed")));
results[i] = result;
}
}
}

// Safe ETH transfer pattern
contract SafeTransfer {
function safeTransferETH(address to, uint256 amount) internal {
(bool success, ) = to.call{value: amount}("");
require(success, "ETH transfer failed");
}

// Handling contract tanpa receive/fallback
function forceTransfer(address to, uint256 amount) internal {
if (!payable(to).send(amount)) {
// Fallback: Use SELFDESTRUCT pattern (deprecated)
// Or create intermediate contract
}
}
}

Demo Deploy Hardhat Ignition (09:45 - 10:00)​

Modern Deployment Workflow​

Hardhat Ignition menyediakan declarative deployment system yang trackable dan reproducible.

Setup Configuration​

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-verify";
import { vars } from "hardhat/config";
import "hardhat-gas-reporter";
import "solidity-coverage";

const config: HardhatUserConfig = {
solidity: {
version: "0.8.28",
settings: {
metadata: {
bytecodeHash: "none", // ⬅ prevents IPFS-hash mismatch
useLiteralContent: true, // ⬅ embeds the full source in metadata
},
optimizer: {
enabled: true,
runs: 200
}
}
},
sourcify: {
enabled: true,
apiUrl: "https://sourcify-api-monad.blockvision.org",
browserUrl: "https://testnet.monadexplorer.com",
},
networks: {
// Konfigurasi untuk localhost development
hardhat: {
chainId: 10143,
},
// Konfigurasi untuk Monad Testnet
monadTestnet: {
url: "https://testnet-rpc.monad.xyz/",
chainId: 10143,
accounts: vars.has("PRIVATE_KEY") ? [`0x${vars.get("PRIVATE_KEY")}`] : [],
gasPrice: "auto",
}
},
etherscan: {
enabled: false,
},
gasReporter: {
enabled: true,
currency: 'USD',
outputFile: "gas-report.txt",
noColors: true,
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts"
}
};

export default config;

Ignition Module Structure​

// ignition/modules/MultiSigDeploy.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

module.exports = buildModule("MultiSigModule", (m) => {
// Parameters untuk flexible deployment
const owners = m.getParameter("owners", [
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8", // Ketua BEM
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", // Bendahara
"0x90F79bf6EB2c4f870365E785982E1f101E93b906" // Sekretaris
]);

const requiredConfirmations = m.getParameter("required", 2);

// Deploy main contract
const multiSig = m.contract("BEMMultiSigWallet", [owners, requiredConfirmations]);

// Optional: Deploy related contracts
const treasury = m.contract("TreasuryManager", [multiSig]);

return { multiSig, treasury };
});

Deployment Commands​

# Deploy ke local hardhat network
npx hardhat ignition deploy ignition/modules/MultiSigDeploy.js

# Deploy ke Sepolia dengan parameters
npx hardhat ignition deploy ignition/modules/MultiSigDeploy.js \
--network monadTestnet \
--parameters '{"owners": ["0x...", "0x...", "0x..."], "required": 2}'

# Verify contract setelah deployment
npx hardhat ignition verify deployment-id --network monadTestnet

Coding & Unit-Test MultiSig BEM (10:00 - 12:00)​

Tantangan 1: BEMMultiSigWallet Implementation​

Real-World Scenario: BEM mengelola dana dari berbagai sumber (sponsor, universitas, donasi) dan memerlukan sistem transparan untuk pengeluaran dana.

Contract Template dengan Hints​

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/* ===============================================================
📝 TUGAS BOOTCAMP
Implementasikan multisig 2-of-3 untuk treasury BEM.
Lengkapi bagian‐bagian bertanda TODO: di bawah ini.
============================================================== */
contract BEMMultiSigSimple {
/* ========== EVENTS ========== */
// Sudah disiapkan, tidak perlu diubah
event Deposit(address indexed sender, uint256 amount);
event SubmitTx(uint256 indexed txId, address indexed to, uint256 value, bytes data);
event ConfirmTx(address indexed owner, uint256 indexed txId);
event ExecuteTx(uint256 indexed txId, bool success);

/* ========== STATE ========== */
// TODO: 1) simpan 3 owner, 2) mapping isOwner, 3) constant QUORUM = 2
// Hint: gunakan address[3] agar fixed-size.

struct TxInfo {
/* TODO:
- destination address
- ETH value
- calldata (bytes)
- executed flag
- jumlah konfirmasi (uint8)
*/
}

// TODO: deklarasi array dynamic txs dan mapping confirmed[txId][owner]

/* ========== MODIFIERS ========== */
// TODO: onlyOwner, txExists, notExecuted, notConfirmed
// Hint: gunakan require() dengan pesan error singkat.

/* ========== CONSTRUCTOR ========== */
/// @param _owners array 3 alamat (Ketua, Bendahara, Sekretaris)
constructor(address[3] memory _owners) {
/* TODO:
- Validasi alamat tidak 0x0
- Cek duplikat
- Set owners[] & isOwner
*/
}

/* ========== RECEIVE ETHER ========== */
receive() external payable {
// TODO: emit Deposit event
}

/* ========== CORE FUNCTIONS ========== */

/// @notice Owner mengajukan transaksi baru
/// @dev Simpan TxInfo & emit SubmitTx
function submit(address _to, uint256 _value, bytes calldata _data)
external /* TODO: tambahkan modifier */
{
/* TODO:
- Buat txId (length array)
- push() struct ke array
- emit event
*/
}

/// @notice Owner menandatangani transaksi
function confirm(uint256 _id)
external /* TODO: lengkapi modifier chain */
{
/* TODO:
- tandai confirmed
- increment confirmations
- emit event
*/
}

/// @notice Eksekusi setelah konfirmasi ≥ QUORUM
function execute(uint256 _id)
external /* TODO: lengkapi modifier chain */
{
/* TODO:
- pastikan quorum
- set executed = true SEBELUM call
- lakukan low-level call .call{value:_value}(_data)
- emit event & revert jika gagal
*/
}

/* ========== GETTERS (view) ========== */

function txCount() external view returns (uint256) {
// TODO: return panjang array txs
}

function getTx(uint256 _id)
external view
returns (address to, uint256 value, bytes memory data, bool executed, uint8 conf)
{
// TODO: kembalikan seluruh isi struct
}
}

Kuliah — Smart Contract Modular Architecture (13:30 - 14:30)​

Token Standards Overview​

Ekosistem kampus digital memerlukan berbagai jenis token untuk different use cases.

ERC-20: Fungible Tokens​

Use Cases di Kampus:

  • Campus Credit: Saldo untuk kafetaria, printing, laundry
  • Event Points: Reward partisipasi kegiatan kampus
  • Library Tokens: Peminjaman buku dan akses resources
// Interface ERC-20 standard
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}

ERC-721: Non-Fungible Tokens (NFTs)​

Use Cases di Kampus:

  • Student ID Cards: Kartu mahasiswa digital dengan expiry
  • Achievement Certificates: Sertifikat prestasi yang unique
  • Event Tickets: Tiket acara kampus yang tidak bisa diduplikasi
// Interface ERC-721 core functions
interface IERC721 {
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}

ERC-1155: Multi-Token Standard​

Use Cases di Kampus:

  • Course Materials: Different types (video, PDF, quiz) dalam satu contract
  • Mixed Rewards: Kombinasi fungible points dan unique badges
  • Semester Package: Bundle berbagai resources mahasiswa
// Interface ERC-1155 highlights
interface IERC1155 {
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;

event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
}

Modular Architecture dengan OpenZeppelin​

OpenZeppelin menyediakan battle-tested implementations dengan modular extensions.

Common Extensions & Patterns​

1. Access Control

import "@openzeppelin/contracts/access/AccessControl.sol";

contract KampusToken is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

constructor() ERC20("Kampus Token", "KMP") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
_grantRole(BURNER_ROLE, msg.sender);
}

function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
}

2. Pausable Pattern

import "@openzeppelin/contracts/security/Pausable.sol";

contract EmergencyPausable is ERC20, Pausable, Ownable {
function pause() public onlyOwner {
_pause();
}

function unpause() public onlyOwner {
_unpause();
}

function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override whenNotPaused {
super._beforeTokenTransfer(from, to, amount);
}
}

3. Snapshot Mechanism

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";

contract VotingToken is ERC20, ERC20Snapshot {
function snapshot() public onlyOwner returns (uint256) {
return _snapshot();
}

// Get balance at specific snapshot
function balanceOfAt(address account, uint256 snapshotId)
public view returns (uint256)
{
return super.balanceOfAt(account, snapshotId);
}
}

Design Patterns untuk Kampus Use Cases​

1. Role-Based Minting Pattern​

contract CampusCredits is ERC20, AccessControl {
// Different roles untuk different departments
bytes32 public constant KAFETARIA_MINTER = keccak256("KAFETARIA_MINTER");
bytes32 public constant LIBRARY_MINTER = keccak256("LIBRARY_MINTER");
bytes32 public constant ADMIN_MINTER = keccak256("ADMIN_MINTER");

// Rate limits per role
mapping(bytes32 => uint256) public roleMintLimit;
mapping(bytes32 => uint256) public lastMintTime;
mapping(bytes32 => uint256) public mintedToday;

constructor() ERC20("Campus Credits", "CREDIT") {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);

// Set daily limits
roleMintLimit[KAFETARIA_MINTER] = 10000 * 10**decimals();
roleMintLimit[LIBRARY_MINTER] = 5000 * 10**decimals();
roleMintLimit[ADMIN_MINTER] = type(uint256).max;
}

function mintWithLimit(address to, uint256 amount) public {
bytes32 role = _getRoleOfSender();
require(hasRole(role, msg.sender), "Unauthorized minter");

// Check daily limit
if (block.timestamp > lastMintTime[role] + 1 days) {
mintedToday[role] = 0;
lastMintTime[role] = block.timestamp;
}

require(mintedToday[role] + amount <= roleMintLimit[role], "Exceeds daily limit");
mintedToday[role] += amount;

_mint(to, amount);
}
}

2. Time-Bound NFT Pattern​

contract StudentIDCard is ERC721, ERC721URIStorage {
struct StudentInfo {
string nim;
string program;
uint256 enrollmentYear;
uint256 expiryDate;
bool isActive;
}

mapping(uint256 => StudentInfo) public studentInfo;
mapping(string => uint256) public nimToTokenId;

function issueStudentID(
address student,
string memory nim,
string memory program,
string memory tokenURI
) public onlyRole(REGISTRAR_ROLE) {
require(nimToTokenId[nim] == 0, "NIM already registered");

uint256 tokenId = _nextTokenId++;
uint256 expiryDate = block.timestamp + (4 * 365 days);

_safeMint(student, tokenId);
_setTokenURI(tokenId, tokenURI);

studentInfo[tokenId] = StudentInfo({
nim: nim,
program: program,
enrollmentYear: block.timestamp,
expiryDate: expiryDate,
isActive: true
});

nimToTokenId[nim] = tokenId;
}

function renewStudentID(uint256 tokenId) public onlyRole(REGISTRAR_ROLE) {
require(_exists(tokenId), "Token does not exist");
StudentInfo storage info = studentInfo[tokenId];
require(info.isActive, "Student not active");

info.expiryDate = block.timestamp + (365 days);
emit StudentIDRenewed(tokenId, info.expiryDate);
}

function deactivateStudent(uint256 tokenId) public onlyRole(REGISTRAR_ROLE) {
studentInfo[tokenId].isActive = false;
emit StudentDeactivated(tokenId);
}
}

3. Composite Token Pattern (ERC-1155)​

contract UniversityAssets is ERC1155, AccessControl {
// Token ID ranges untuk different categories
uint256 constant COURSE_MATERIALS_START = 1000;
uint256 constant CERTIFICATES_START = 2000;
uint256 constant EVENT_TICKETS_START = 3000;
uint256 constant MERCHANDISE_START = 4000;

// Metadata untuk each token type
struct TokenMetadata {
string name;
string category;
bool isTransferable;
bool isBurnable;
uint256 maxSupply;
uint256 currentSupply;
}

mapping(uint256 => TokenMetadata) public tokenMetadata;

function createCourseaterial(
uint256 courseId,
string memory materialName,
uint256 maxSupply
) public onlyRole(PROFESSOR_ROLE) returns (uint256) {
uint256 tokenId = COURSE_MATERIALS_START + courseId;

tokenMetadata[tokenId] = TokenMetadata({
name: materialName,
category: "Course Material",
isTransferable: false,
isBurnable: true,
maxSupply: maxSupply,
currentSupply: 0
});

emit CourseaterialCreated(tokenId, materialName);
return tokenId;
}

function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts
) public {
for (uint i = 0; i < ids.length; i++) {
TokenMetadata storage metadata = tokenMetadata[ids[i]];
require(
metadata.currentSupply + amounts[i] <= metadata.maxSupply,
"Exceeds max supply"
);
metadata.currentSupply += amounts[i];
}

_mintBatch(to, ids, amounts, "");
}

function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal override {
// Check transferability for each token
for (uint i = 0; i < ids.length; i++) {
if (from != address(0) && to != address(0)) {
require(
tokenMetadata[ids[i]].isTransferable,
"Token not transferable"
);
}
}

super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
}
}

ERC Suite Challenge (14:30 - 16:00)​

Tantangan 2: Kampus Token Suite Implementation​

Implementasikan tiga contract token untuk ekosistem kampus digital dengan real-world functionality.

KontrakNamaFungsi KampusFitur Tambahan
ERC-20CampusCreditSaldo kafetaria / printingpause() / unpause()
ERC-721StudentIDKartu identitas NFT (kadaluwarsa 4 thn)Metadata URI, burnExpired()
ERC-1155CourseBadgeSertifikat mata kuliah & tiket acaraBatch mint, role MINTER/PAUSER

1. CampusCredit (ERC-20) Implementation​

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

/**
* @title CampusCredit
* @dev ERC-20 token untuk transaksi dalam kampus
* Use cases:
* - Pembayaran di kafetaria
* - Biaya printing dan fotokopi
* - Laundry service
* - Peminjaman equipment
*/
contract CampusCredit is ERC20, ERC20Burnable, Pausable, AccessControl {
// TODO: Define role constants
// bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
// bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

// Additional features untuk kampus
mapping(address => uint256) public dailySpendingLimit;
mapping(address => uint256) public spentToday;
mapping(address => uint256) public lastSpendingReset;

// Merchant whitelist
mapping(address => bool) public isMerchant;
mapping(address => string) public merchantName;

constructor() ERC20("Campus Credit", "CREDIT") {
// TODO: Setup roles
// Hint:
// 1. Grant DEFAULT_ADMIN_ROLE ke msg.sender
// 2. Grant PAUSER_ROLE ke msg.sender
// 3. Grant MINTER_ROLE ke msg.sender
// 4. Consider initial mint untuk treasury
}

/**
* @dev Pause all token transfers
* Use case: Emergency atau maintenance
*/
function pause() public {
// TODO: Implement dengan role check
// Only PAUSER_ROLE can pause
}

function unpause() public {
// TODO: Implement unpause
}

/**
* @dev Mint new tokens
* Use case: Top-up saldo mahasiswa
*/
function mint(address to, uint256 amount) public {
// TODO: Implement dengan role check
// Only MINTER_ROLE can mint
// Consider adding minting limits
}

/**
* @dev Register merchant
* Use case: Kafetaria, toko buku, laundry
*/
function registerMerchant(address merchant, string memory name)
public onlyRole(DEFAULT_ADMIN_ROLE)
{
// TODO: Register merchant untuk accept payments
}

/**
* @dev Set daily spending limit untuk mahasiswa
* Use case: Parental control atau self-control
*/
function setDailyLimit(address student, uint256 limit)
public onlyRole(DEFAULT_ADMIN_ROLE)
{
// TODO: Set spending limit
}

/**
* @dev Transfer dengan spending limit check
*/
function transferWithLimit(address to, uint256 amount) public {
// TODO: Check daily limit before transfer
// Reset limit if new day
// Update spent amount
// Then do normal transfer
}

/**
* @dev Override _beforeTokenTransfer untuk add pause functionality
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override(ERC20) {
// TODO: Add pause check
// super._beforeTokenTransfer(from, to, amount);
// require(!paused(), "Token transfers paused");
}

/**
* @dev Cashback mechanism untuk encourage usage
*/
uint256 public cashbackPercentage = 2; // 2%

function transferWithCashback(address merchant, uint256 amount) public {
// TODO: Transfer to merchant dengan cashback ke sender
// Calculate cashback
// Transfer main amount
// Mint cashback to sender
}
}

2. StudentID (ERC-721) Implementation​

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
* @title StudentID
* @dev NFT-based student identity card
* Features:
* - Auto-expiry after 4 years
* - Renewable untuk active students
* - Contains student metadata
* - Non-transferable (soulbound)
*/
contract StudentID is ERC721, ERC721URIStorage, ERC721Burnable, Ownable {
uint256 private _nextTokenId;

struct StudentData {
string nim;
string name;
string major;
uint256 enrollmentYear;
uint256 expiryDate;
bool isActive;
uint8 semester;
}

// TODO: Add mappings
// mapping(uint256 => StudentData) public studentData;
// mapping(string => uint256) public nimToTokenId; // Prevent duplicate NIM
// mapping(address => uint256) public addressToTokenId; // One ID per address

// Events
event StudentIDIssued(
uint256 indexed tokenId,
string nim,
address student,
uint256 expiryDate
);
event StudentIDRenewed(uint256 indexed tokenId, uint256 newExpiryDate);
event StudentStatusUpdated(uint256 indexed tokenId, bool isActive);
event ExpiredIDBurned(uint256 indexed tokenId);

constructor() ERC721("Student Identity Card", "SID") Ownable(msg.sender) {}

/**
* @dev Issue new student ID
* Use case: New student enrollment
*/
function issueStudentID(
address to,
string memory nim,
string memory name,
string memory major,
string memory uri
) public onlyOwner {
// TODO: Implement ID issuance
// Hints:
// 1. Check NIM tidak duplicate (use nimToTokenId)
// 2. Check address belum punya ID (use addressToTokenId)
// 3. Calculate expiry (4 years from now)
// 4. Mint NFT
// 5. Set token URI (foto + metadata)
// 6. Store student data
// 7. Update mappings
// 8. Emit event
}

/**
* @dev Renew student ID untuk semester baru
*/
function renewStudentID(uint256 tokenId) public onlyOwner {
// TODO: Extend expiry date
// Check token exists
// Check student is active
// Add 6 months to expiry
// Update semester
// Emit renewal event
}

/**
* @dev Update student status (active/inactive)
* Use case: Cuti, DO, atau lulus
*/
function updateStudentStatus(uint256 tokenId, bool isActive) public onlyOwner {
// TODO: Update active status
// If inactive, maybe reduce privileges
}

/**
* @dev Burn expired IDs
* Use case: Cleanup expired cards
*/
function burnExpired(uint256 tokenId) public {
// TODO: Allow anyone to burn if expired
// Check token exists
// Check if expired (block.timestamp > expiryDate)
// Burn token
// Clean up mappings
// Emit event
}

/**
* @dev Check if ID is expired
*/
function isExpired(uint256 tokenId) public view returns (bool) {
// TODO: Return true if expired
}

/**
* @dev Get student info by NIM
*/
function getStudentByNIM(string memory nim) public view returns (
address owner,
uint256 tokenId,
StudentData memory data
) {
// TODO: Lookup student by NIM
}

/**
* @dev Override transfer functions to make non-transferable
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override {
// TODO: Make soulbound (non-transferable)
// Only allow minting (from == address(0)) and burning (to == address(0))
// require(from == address(0) || to == address(0), "SID is non-transferable");
// super._beforeTokenTransfer(from, to, tokenId, batchSize);
}

// Override functions required untuk multiple inheritance
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
// TODO: Clean up student data when burning
// delete studentData[tokenId];
// delete nimToTokenId[studentData[tokenId].nim];
// delete addressToTokenId[ownerOf(tokenId)];
}
}

3. CourseBadge (ERC-1155) Implementation​

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";

/**
* @title CourseBadge
* @dev Multi-token untuk berbagai badges dan certificates
* Token types:
* - Course completion certificates (non-fungible)
* - Event attendance badges (fungible)
* - Achievement medals (limited supply)
* - Workshop participation tokens
*/
contract CourseBadge is ERC1155, AccessControl, Pausable, ERC1155Supply {
// Role definitions
bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

// Token ID ranges untuk organization
uint256 public constant CERTIFICATE_BASE = 1000;
uint256 public constant EVENT_BADGE_BASE = 2000;
uint256 public constant ACHIEVEMENT_BASE = 3000;
uint256 public constant WORKSHOP_BASE = 4000;

// Token metadata structure
struct TokenInfo {
string name;
string category;
uint256 maxSupply;
bool isTransferable;
uint256 validUntil; // 0 = no expiry
address issuer;
}

// TODO: Add mappings
// mapping(uint256 => TokenInfo) public tokenInfo;
// mapping(uint256 => string) private _tokenURIs;

// Track student achievements
// mapping(address => uint256[]) public studentBadges;
// mapping(uint256 => mapping(address => uint256)) public earnedAt; // Timestamp

// Counter untuk generate unique IDs
uint256 private _certificateCounter;
uint256 private _eventCounter;
uint256 private _achievementCounter;
uint256 private _workshopCounter;

constructor() ERC1155("") {
// TODO: Setup roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(URI_SETTER_ROLE, msg.sender);
_grantRole(PAUSER_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}

/**
* @dev Create new certificate type
* Use case: Mata kuliah baru atau program baru
*/
function createCertificateType(
string memory name,
uint256 maxSupply,
string memory uri
) public onlyRole(MINTER_ROLE) returns (uint256) {
// TODO: Create new certificate type
// 1. Generate ID: CERTIFICATE_BASE + _certificateCounter++
// 2. Store token info
// 3. Set URI
// 4. Return token ID
}

/**
* @dev Issue certificate to student
* Use case: Student lulus mata kuliah
*/
function issueCertificate(
address student,
uint256 certificateType,
string memory additionalData
) public onlyRole(MINTER_ROLE) {
// TODO: Mint certificate
// 1. Verify certificate type exists
// 2. Check max supply not exceeded
// 3. Mint 1 token to student
// 4. Record timestamp
// 5. Add to student's badge list
}

/**
* @dev Batch mint event badges
* Use case: Attendance badges untuk peserta event
*/
function mintEventBadges(
address[] memory attendees,
uint256 eventId,
uint256 amount
) public onlyRole(MINTER_ROLE) {
// TODO: Batch mint to multiple addresses
// Use loop to mint to each attendee
// Record participation
}

/**
* @dev Set metadata URI untuk token
*/
function setTokenURI(uint256 tokenId, string memory newuri)
public onlyRole(URI_SETTER_ROLE)
{
// TODO: Store custom URI per token
}

/**
* @dev Get all badges owned by student
*/
function getStudentBadges(address student)
public view returns (uint256[] memory)
{
// TODO: Return array of token IDs owned by student
}

/**
* @dev Verify badge ownership dengan expiry check
*/
function verifyBadge(address student, uint256 tokenId)
public view returns (bool isValid, uint256 earnedTimestamp)
{
// TODO: Check ownership and validity
// 1. Check balance > 0
// 2. Check not expired
// 3. Return status and when earned
}

/**
* @dev Pause all transfers
*/
function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}

function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}

/**
* @dev Override transfer to check transferability and pause
*/
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal override(ERC1155, ERC1155Supply) whenNotPaused {
// TODO: Check transferability for each token
// for (uint i = 0; i < ids.length; i++) {
// if (from != address(0) && to != address(0)) { // Not mint or burn
// require(tokenInfo[ids[i]].isTransferable, "Token not transferable");
// }
// }

super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
}

/**
* @dev Override to return custom URI per token
*/
function uri(uint256 tokenId) public view override returns (string memory) {
// TODO: Return stored URI for token
// return _tokenURIs[tokenId];
}

/**
* @dev Check interface support
*/
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC1155, AccessControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

// Achievement System Functions

/**
* @dev Grant achievement badge
* Use case: Dean's list, competition winner, etc
*/
function grantAchievement(
address student,
string memory achievementName,
uint256 rarity // 1 = common, 2 = rare, 3 = legendary
) public onlyRole(MINTER_ROLE) returns (uint256) {
// TODO: Create unique achievement NFT
// Generate achievement ID
// Set limited supply based on rarity
// Mint to deserving student
}

/**
* @dev Create workshop series dengan multiple sessions
*/
function createWorkshopSeries(
string memory seriesName,
uint256 totalSessions
) public onlyRole(MINTER_ROLE) returns (uint256[] memory) {
// TODO: Create multiple related tokens
// Return array of token IDs for each session
}
}

Deployment dengan Hardhat Ignition​

// ignition/modules/TokenSuite.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

module.exports = buildModule("TokenSuiteModule", (m) => {
// Deploy ERC-20: CampusCredit
const campusCredit = m.contract("CampusCredit");

// Deploy ERC-721: StudentID
const studentID = m.contract("StudentID");

// Deploy ERC-1155: CourseBadge
const courseBadge = m.contract("CourseBadge");

// Optional: Setup initial configuration
m.call(campusCredit, "registerMerchant", [
"0x...", // Kafetaria address
"Kafetaria Kampus"
]);

return { campusCredit, studentID, courseBadge };
});

Sesi Evaluasi & Review (16:00 - 16:30)​

Demo & Presentation​

Contract Demonstration Checklist:

  1. MultiSig Wallet Operations

    • Show 2-of-3 approval flow on Etherscan
    • Demonstrate failed execution with insufficient approvals
    • Display transaction history and status
  2. Token Functionality

    • Mint CampusCredit and show balance
    • Issue StudentID NFT with metadata
    • Batch mint CourseBadge for event
  3. Integration Scenarios

    • MultiSig controlling token contract ownership
    • Treasury funding student wallets
    • Badge verification system

Technical Quiz​

  1. Apa perbedaan utama antara require(), revert(), dan assert() dalam error handling?

    • require(): Pre-condition checks, refund gas
    • revert(): Custom errors, flexible
    • assert(): Invariants, consumes all gas
  2. Mengapa menggunakan library dalam Solidity? Berikan contoh use case.

    • Code reuse, gas efficiency, cleaner contracts
    • Example: SafeMath, Address utils
  3. Jelaskan konsep function selector dan bagaimana digunakan dalam low-level calls.

    • First 4 bytes of keccak256 hash
    • Used to identify which function to call
  4. Apa keuntungan ERC-1155 dibanding ERC-20 dan ERC-721?

    • Single contract for multiple token types
    • Batch operations
    • Gas efficient for games/complex systems
  5. Bagaimana cara implement pause mechanism yang aman?

    • Use OpenZeppelin Pausable
    • Role-based pause control
    • Override transfer functions

Gas Optimization Analysis​

Tips untuk Optimize Gas:

// Bad: Multiple storage reads
function bad() public {
for (uint i = 0; i < owners.length; i++) {
if (owners[i] == msg.sender) {
// do something
}
}
}

// Good: Cache array length
function good() public {
uint256 length = owners.length;
for (uint i = 0; i < length; i++) {
if (owners[i] == msg.sender) {
// do something
}
}
}

// Better: Use mapping for O(1) lookup
mapping(address => bool) public isOwner;

Security Best Practices Review​

Critical Security Patterns:

  1. Reentrancy Protection

    // Update state BEFORE external call
    balances[msg.sender] -= amount;
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
  2. Access Control

    // Use role-based access from OpenZeppelin
    onlyRole(MINTER_ROLE)
  3. Integer Overflow (Solidity 0.8+ has built-in protection)

  4. Front-running Prevention

    • Use commit-reveal schemes
    • Time-locks for sensitive operations

Peer Review Session​

Code Review Checklist:

  • Proper error handling implemented
  • Access control on all admin functions
  • Events emitted for all state changes
  • Gas-efficient patterns used
  • No security vulnerabilities
  • Clear documentation and comments

Deliverables​

1. Repo GitHub sesi-2-digital-kampus/​

contracts/
├─ BEMMultiSigWallet.sol
├─ CampusCredit.sol
├─ StudentID.sol
└─ CourseBadge.sol
ignition/
├─ modules/
│ ├─ MultiSigDeploy.js
│ └─ TokenSuite.js
test/
├─ BEMMultiSigWallet.test.js
├─ CampusCredit.test.js
├─ StudentID.test.js
└─ CourseBadge.test.js
scripts/
├─ deploy.js
└─ verify.js
.env.example
hardhat.config.js
package.json
README.md

2. Kontrak ter-verifikasi di Sepolia Etherscan​

Verification Checklist:

  • BEMMultiSigWallet deployed & verified
  • CampusCredit deployed & verified
  • StudentID deployed & verified
  • CourseBadge deployed & verified
  • All source code readable on Etherscan

3. REFLEKSI.md​

# Refleksi Pembelajaran Sesi 2

## Konsep yang Dipelajari

### Solidity 104: Error Handling & Code Organization
- [Jelaskan pemahaman tentang require, revert, assert]
- [Bagaimana libraries membantu code reuse]
- [Penggunaan inheritance dalam kontrak]
- [Peran interfaces dalam modular architecture]

### Solidity 105: Special Functions & Low-Level Operations
- [Global variables yang paling berguna]
- [Pemahaman tentang ABI dan function selectors]
- [Kapan menggunakan low-level calls]

### Multi-Signature Wallet
- [Mengapa penting untuk treasury management]
- [Challenge dalam implementasi]
- [Real-world applications]

### Token Standards
- [Perbedaan use case ERC-20, 721, 1155]
- [Pentingnya OpenZeppelin]
- [Security considerations]

## Hambatan Teknis

### Challenge 1: Multi-Sig Implementation
- [Kesulitan yang dihadapi]
- [Bagaimana mengatasi]

### Challenge 2: Token Suite
- [Complex inheritance issues]
- [Gas optimization challenges]

## Solusi & Learning

### Problem Solving Approach
- [Strategi debugging]
- [Resource yang membantu]
- [Collaboration dengan peers]

## Rencana Kedepan

### Skills untuk Ditingkatkan
- [ ] Gas optimization techniques
- [ ] Advanced security patterns
- [ ] Testing strategies

### Proyek Lanjutan
- [Ide untuk extend contracts]
- [Integration possibilities]

Resources​

Documentation​

Security Resources​

Testing Resources​

Gas Optimization​


Kesimpulan Sesi 2​

🏆 Achievement Unlocked! Anda telah berhasil membangun fondasi governance dan ekonomi token untuk ekosistem kampus digital.

Pencapaian Hari Ini:

  • 🏛️ Implementasi multi-sig wallet untuk transparansi keuangan BEM
  • 💰 Deploy sistem token untuk ekonomi internal kampus
  • 🎓 Create NFT identity system untuk mahasiswa
  • 🏅 Build multi-token platform untuk achievements
  • 🔒 Master advanced Solidity patterns dan security

Skills yang Dikuasai:

  • Error handling & defensive programming
  • Modular architecture dengan libraries & inheritance
  • Low-level operations untuk advanced functionality
  • Token standards implementation (ERC-20/721/1155)
  • Role-based access control
  • Gas optimization awareness

Ekosistem digital kampus kini memiliki:

  • Financial Layer: Multi-sig treasury untuk governance
  • Economic Layer: Fungible tokens untuk transaksi
  • Identity Layer: NFT untuk verifikasi mahasiswa
  • Achievement Layer: Multi-token untuk badges & certificates

Persiapan Sesi 3: Frontend integration akan menghidupkan smart contracts ini dengan user interface yang intuitif. Account Abstraction akan membuat Web3 accessible untuk semua mahasiswa, bahkan yang non-technical!


---
id: sesi-3
title: "Sesi 3: Frontend Integration + Account Abstraction (AA)"
sidebar_label: "Sesi 3: Frontend Integration + Account Abstraction (AA)"
description: "Integrasi frontend Web3 modern dan implementasi Account Abstraction (ERC-4337)"
---

"Code without users is just philosophy. Mari kita build bridges antara smart contracts dan real users!" 🚀