Arsitektur tokenisasi emas & saham (RWA)
Ini titik kumpul Fase 1 dan Fase 2. Tokenisasi RWA bukan cuma 'deploy kontrak ERC-20' — ia adalah sistem hybrid yang separuhnya di dunia nyata (kustodian, auditor) dan separuhnya di blockchain, dijembatani oleh proses yang harus dirancang sengaja.
Intisari
- Tokenisasi RWA selalu punya dua sisi: off-chain (aset fisik/legal di dunia nyata) dan on-chain (representasi token) — blockchain tidak menghilangkan kebutuhan kepercayaan pada kustodian, ia cuma membuat representasi kepemilikannya transparan dan bisa diaudit.
- Oracle adalah jembatan satu arah dari dunia off-chain ke on-chain — smart contract tidak bisa "menelepon" API kustodian sendiri, data harus didorong masuk lewat transaksi.
- Pola
mintsaat aset masuk,burnsaat dicairkan menjagatotalSupply()token selalu mencerminkan jumlah aset fisik yang benar-benar ada di brankas. - KYC wajib jadi syarat mint dan transfer — bukan cuma saat pendaftaran awal — persis prinsip ERC-3643 di materi sebelumnya.
- Titik kegagalan paling realistis bukan smart contract-nya, melainkan integritas kustodian: kalau auditor berbohong soal emas yang ada di brankas, tidak ada baris Solidity yang bisa mendeteksinya.
Diagram alur tokenisasi
┌─────────────────────────── OFF-CHAIN (dunia nyata) ───────────────────────────┐
│ Emas fisik di brankas <──────> Kustodian / Auditor │
│ (atau saham di KSEI) verifikasi ketersediaan aset │
└──────────────────────────────────────┬────────────────────────────────────────┘
│ oracle / proses admin terverifikasi
▼
┌─────────────────────────── ON-CHAIN (blockchain) ─────────────────────────────┐
│ GoldToken (ERC-20 + kepatuhan ala ERC-3643) │
│ mint(ke, jumlah, buktiBrankas) → token baru diterbitkan sesuai aset masuk│
│ redeemGold(jumlah, idPencairan) → token dibakar saat dicairkan jadi fisik │
│ isKYCApproved[address] → syarat mint dan transfer │
└──────────────────────────────────────┬────────────────────────────────────────┘
│ JSON-RPC / web3j / web3.php
▼
┌───────────────────── BACKEND & CLIENT (Fase 3) ────────────────────────────────┐
│ Java/PHP backend (API, MySQL, proses KYC) ⇄ Wallet pengguna │
└─────────────────────────────────────────────────────────────────────────────────┘
Kenapa butuh oracle
Ingat dari Fase 0: EVM sepenuhnya deterministik dan tidak punya akses I/O eksternal. Smart contract tidak bisa bertanya sendiri ke API kustodian "berapa gram emas yang ada di brankas hari ini?" — data itu harus didorong masuk lewat transaksi yang ditandatangani pihak yang berwenang (admin/agent terverifikasi, atau jaringan oracle terdesentralisasi untuk kasus yang lebih ketat).
Ini batas nyata dari "trustless". Blockchain membuat representasi kepemilikan token transparan dan tidak bisa dipalsukan diam-diam. Tapi ia tidak bisa memverifikasi sendiri bahwa emas fisiknya benar-benar ada di brankas — itu tetap bergantung pada integritas kustodian dan auditor off-chain. Sistem tokenisasi RWA yang jujur akan bilang: "blockchain membuat catatan kepemilikan transparan", bukan "blockchain menghilangkan kebutuhan mempercayai siapa pun".
Kontrak lengkap: GoldToken
Menggabungkan pola dari seluruh Fase 1 dan 2: AccessControl untuk peran admin/KYC-officer,
ERC-20 sebagai fondasi fungible, custom error untuk hemat gas, dan modifier kepatuhan ala ERC-3643.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract GoldToken is ERC20, AccessControl {
bytes32 public constant KYC_OFFICER_ROLE = keccak256("KYC_OFFICER_ROLE");
bytes32 public constant CUSTODIAN_ROLE = keccak256("CUSTODIAN_ROLE");
mapping(address => bool) public kycTerverifikasi;
event EmasDiterbitkan(address indexed ke, uint256 gram, string buktiBrankas);
event EmasDicairkan(address indexed dari, uint256 gram, string idPencairan);
event StatusKYCDiubah(address indexed pengguna, bool status);
error BelumKYC(address alamat);
modifier hanyaKYC(address alamat) {
if (!kycTerverifikasi[alamat]) revert BelumKYC(alamat);
_;
}
constructor(address admin) ERC20("Gold Token Physical", "XAU") {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(KYC_OFFICER_ROLE, admin);
_grantRole(CUSTODIAN_ROLE, admin);
}
function setStatusKYC(address pengguna, bool status) external onlyRole(KYC_OFFICER_ROLE) {
kycTerverifikasi[pengguna] = status;
emit StatusKYCDiubah(pengguna, status);
}
// Dipanggil admin/backend SETELAH kustodian mengonfirmasi emas fisik masuk brankas.
// "buktiBrankas" merujuk ke dokumen off-chain (nomor resi, laporan auditor).
function mintGold(address ke, uint256 gram, string calldata buktiBrankas)
external
onlyRole(CUSTODIAN_ROLE)
hanyaKYC(ke)
{
_mint(ke, gram);
emit EmasDiterbitkan(ke, gram, buktiBrankas);
}
// Pengguna membakar token miliknya sendiri saat menukarkan dengan emas fisik.
function redeemGold(uint256 gram, string calldata idPencairan) external hanyaKYC(msg.sender) {
_burn(msg.sender, gram);
emit EmasDicairkan(msg.sender, gram, idPencairan);
}
// Override hook internal ERC20 — satu titik yang dilewati SEMUA perpindahan
// saldo (mint, burn, transfer biasa), jadi syarat KYC berlaku di mana-mana.
function _update(address dari, address ke, uint256 nilai) internal override {
if (dari != address(0)) {
if (!kycTerverifikasi[dari]) revert BelumKYC(dari);
}
if (ke != address(0)) {
if (!kycTerverifikasi[ke]) revert BelumKYC(ke);
}
super._update(dari, ke, nilai);
}
}
Kenapa _update yang di-override, bukan transfer. Fungsi
_update adalah hook internal OpenZeppelin yang dilewati setiap perubahan saldo —
mint (dari address(0)), burn (ke address(0)), dan
transfer biasa. Meng-override di satu titik ini menjamin aturan KYC berlaku konsisten di
semua jalur, bukan cuma jalur yang kamu ingat untuk ditambah pengecekan manual satu-satu.
Siklus hidup token: mint dan burn menjaga totalSupply tetap jujur
| Kejadian off-chain | Aksi on-chain | Efek ke totalSupply |
|---|---|---|
| Emas fisik baru masuk brankas, diverifikasi auditor | mintGold() | Naik |
| Pengguna menukar token dengan emas fisik | redeemGold() | Turun |
| Audit rutin kustodian (tanpa perubahan aset) | Tidak ada — hanya pencatatan off-chain | Tetap |
Invariant yang harus selalu benar: totalSupply() token di blockchain harus sama dengan jumlah
emas fisik di brankas (dikonversi ke satuan yang sama). Menjaga invariant ini benar adalah tanggung jawab
proses off-chain (audit rutin, rekonsiliasi) — bukan sesuatu yang bisa dipaksakan oleh Solidity semata.
Latihan: deploy GoldToken di Remix. Beri CUSTODIAN_ROLE ke satu akun,
KYC_OFFICER_ROLE ke akun lain. Verifikasi KYC dua alamat, mint emas ke salah satunya, lalu
coba transfer ke alamat ketiga yang belum di-KYC — pastikan transaksinya revert
dengan BelumKYC, bukan berhasil diam-diam.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.