Skip to content

Tutorial: random-trait NFTs ​

Source: verity-examples/contracts/src/RandomTraitsNFT.sol (ERC-721, OpenZeppelin v5.0.2).

Mint first, reveal later. The token is minted, and paid for, before its randomness exists. The traits are written when the VRF word arrives, so a minter cannot see the traits and back out, and cannot reroll.

solidity
function mint() external payable returns (uint256 tokenId) {
    tokenId = ++totalMinted;
    _safeMint(msg.sender, tokenId);                // exists, unrevealed
    uint256 requestId = s_coordinator.requestRandomWords(keyHash, subId, confirmations, CALLBACK_GAS_LIMIT, 1);
    tokenOfRequest[requestId] = tokenId;
}

Independent traits from one word ​

solidity
uint256 roll = uint64(w) % 100;                    // bits   0..63   rarity
uint8 background = uint8((w >> 64) % 8);           // bits  64..127
uint8 body       = uint8((w >> 128) % 16);         // bits 128..191
uint8 accessory  = uint8((w >> 192) % 32);         // bits 192..255

Each trait reads its own 64-bit slice, so the traits are independent. (Computing rarity as w % 100 from the whole word would correlate it with the other traits. Our first draft of this example had exactly that bug, and the tests caught it.)

Rarity table: Common 60%, Rare 30%, Epic 9%, Legendary 1%. A test mints 400 tokens and checks the distribution.

PulseChain

Use OpenZeppelin v5.0.x for ERC-721 on PulseChain. Newer versions use the mcopy opcode, which PulseChain does not support. See PulseChain notes.

Testnet: PulseChain testnet v4. Unaudited software; do not use on mainnet yet.