ERC-721 NFT
ERC-721 adalah standar untuk Non-Fungible Token (NFT) — token yang unik dan tidak bisa ditukar 1:1. Setiap token memiliki tokenId yang berbeda dan bisa punya metadata unik (gambar, atribut, dll.).
Perbedaan ERC-20 vs ERC-721
| Aspek | ERC-20 (Fungible) | ERC-721 (Non-Fungible) |
|---|---|---|
| Keunikan | Setiap token identik | Setiap token unik (tokenId) |
| Divisible | Ya (decimals) | Tidak (1 NFT = 1 NFT) |
| Contoh | USDC, UNI, LINK | CryptoPunks, BAYC, Art |
| Transfer | transfer(to, amount) | transferFrom(from, to, tokenId) |
Implementasi NFT dengan OpenZeppelin
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract BelajarNFT is ERC721, ERC721URIStorage, Ownable {
uint256 private _nextTokenId;
uint256 public constant MAX_SUPPLY = 1000;
uint256 public mintPrice = 0.01 ether;
constructor() ERC721("BelajarNFT", "BNFT") Ownable(msg.sender) {}
function mint(string memory uri) public payable {
require(_nextTokenId < MAX_SUPPLY, "Sold out");
require(msg.value >= mintPrice, "Insufficient ETH");
uint256 tokenId = _nextTokenId++;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, uri);
}
// Override required by Solidity
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);
}
}
Metadata Standard
NFT metadata biasanya disimpan di IPFS atau server, dengan format JSON standard:
// tokenURI mengembalikan URL ke JSON ini:
{
"name": "BelajarNFT #42",
"description": "Proof of Learning - Web3 Module",
"image": "ipfs://QmXxx.../42.png",
"attributes": [
{ "trait_type": "Level", "value": "Advanced" },
{ "trait_type": "Module", "value": "Smart Contract Security" },
{ "trait_type": "Score", "value": 95, "display_type": "number" }
]
}
Minting dari Frontend
async function mintNFT() {
const contract = new ethers.Contract(nftAddress, nftAbi, signer);
// Upload metadata ke IPFS terlebih dahulu
const metadataURI = "ipfs://QmYourMetadata...";
const tx = await contract.mint(metadataURI, {
value: ethers.parseEther("0.01"), // mint price
});
const receipt = await tx.wait();
// Ambil tokenId dari event Transfer
const transferEvent = receipt.logs.find(
(log) => log.topics[0] === ethers.id("Transfer(address,address,uint256)")
);
console.log("Minted NFT!");
}
Enumeration & Ownership
ownerOf(tokenId)— Siapa pemilik NFT ini?balanceOf(address)— Berapa NFT yang dimiliki address ini?tokenURI(tokenId)— URL metadata untuk NFT iniapprove(to, tokenId)— Izinkan address lain transfer NFT ini