← Semua pembelajaran / Blockchain Nol → RWA
Fase 2 · Standar Token & Tokenisasi RWA

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.

Sumber asli ethereum.org Resmi Rangkuman ~9 menit baca

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 mint saat aset masuk, burn saat dicairkan menjaga totalSupply() 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-chainAksi on-chainEfek ke totalSupply
Emas fisik baru masuk brankas, diverifikasi auditormintGold()Naik
Pengguna menukar token dengan emas fisikredeemGold()Turun
Audit rutin kustodian (tanpa perubahan aset)Tidak ada — hanya pencatatan off-chainTetap

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.