Memahami aset non-fungible: ERC-721 untuk item unik, ERC-1155 untuk multi-asset, metadata off-chain, mekanisme marketplace dan royalti, plus praktik membangun kontrak NFT lengkap dengan OpenZeppelin

Setelah di episode 9 kalian memahami DeFi, pada episode ini kita membangun aset yang paling dikenal publik: NFT (Non-Fungible Token). Meski citra NFT di memori publik adalah "gambar mahal", kenyataan 2026 jauh lebih luas: tiket, sertifikat, RWA (episode 15), aset game (episode 23), dan membership — semuanya dibangun di atas standard yang sama.
Kita pelajari dua standard utama — ERC-721 dan ERC-1155 — plus metadata, marketplace, dan praktik membangun kontrak NFT production dengan OpenZeppelin.
ERC-721 merepresentasikan aset unik — setiap token punya tokenId berbeda, dan paling banyak satu pemilik per token. Standard ini adalah fondasi seluruh ekosistem NFT.
Inti antarmukanya:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC721 {
function ownerOf(uint256 tokenId) external view returns (address);
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function balanceOf(address owner) external view returns (uint256);
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address);
}Poin penting untuk diingat: transfer NFT tidak pernah memakai transfer — ia memakai safeTransferFrom yang memeriksa apakah penerima adalah kontrak yang memahami ERC-721. Ini mencegah NFT terkunci selamanya di kontrak yang tidak punya handler.
Di production, kalian tidak menulis ERC-721 dari nol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNFT is ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
uint256 public mintPrice = 0.01 ether;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mintNFT(address _to, string memory _tokenURI)
external
payable
returns (uint256)
{
require(msg.value >= mintPrice, "insufficient payment");
_tokenIds.increment();
uint256 newId = _tokenIds.current();
_mint(_to, newId);
_setTokenURI(newId, _tokenURI);
return newId;
}
function setMintPrice(uint256 _price) external onlyOwner {
mintPrice = _price;
}
}Dengan ERC721URIStorage, setiap token menyimpan URI metadata-nya sendiri — fleksibel untuk aset unik.
NFT di-chain hanyalah tokenId + URI. Semua yang terlihat pengguna (gambar, nama, atribut) ada di metadata — biasanya file JSON di IPFS atau server:
{
"name": "MyNFT #1",
"description": "Sertifikat partisipasi workshop blockchain",
"image": "ipfs://QmXYZ.../image.png",
"attributes": [
{ "trait_type": "Tipe", "value": "Bronze" },
{ "trait_type": "Tahun", "value": "2026" }
]
}Bagi developer, metadata ini adalah sumber kebenaran untuk marketplace dan wallet. Standar yang berlaku adalah ERC-721 metadata extension: tokenURI(tokenId) mengembalikan URI JSON di atas.
Warning
Jangan pernah menggantungkan metadata di server yang bisa kalian matikan. Standard praktik 2026: simpan di IPFS (permanen, content-addressed) atau Arweave. URI yang bergantung pada API server berarti NFT kalian bisa "hilang" sewaktu-waktu.
ERC-1155 mengombinasikan fungible dan non-fungible dalam satu kontrak — efisien untuk game dan koleksi multi-item:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MultiAsset is ERC1155, Ownable {
constructor() ERC1155("ipfs://bafybe.../{id}.json") Ownable(msg.sender) {}
function mintBatch(
address _to,
uint256[] memory _ids,
uint256[] memory _amounts
) external onlyOwner {
_mintBatch(_to, _ids, _amounts, "");
}
function uri(uint256 id) public view override returns (string memory) {
// placeholder {id} diganti di runtime oleh marketplace
return super.uri(id);
}
}Satu transaksi bisa mentransfer banyak tipe aset sekaligus — penghematan gas yang signifikan untuk game dengan ratusan item.
Marketplace (OpenSea, Blur) menyediakan tempat jual-beli NFT. Sebagai developer kontrak, hal penting yang harus kalian sediakan adalah royalty — kompensasi pencipta pada setiap transaksi sekunder. Standard 2026: ERC-2981.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
contract RoyaltyNFT is ERC721, IERC2981 {
address public royaltyReceiver;
uint96 public royaltyBps = 500; // 5%
constructor() ERC721("Royal", "RYL") {}
function royaltyInfo(uint256, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount)
{
return (royaltyReceiver, (salePrice * royaltyBps) / 10_000);
}
}Dengan IERC2981, marketplace wajib memanggil royaltyInfo() dan menyalurkan persentase ke pencipta pada setiap penjualan sekunder — royalti on-chain yang tidak bisa diabaikan.
Inti yang harus dibawa pulang:
safeTransferFrom mencegah NFT terkunci.Di episode 11 selanjutnya kita menghubungkan kontrak dengan dunia nyata — Oracles & Data: Chainlink, price feeds, dan cara aman membawa data off-chain ke on-chain. Mari lanjut ke episode 11!