I want to deploy an NFT that at mint time, selects at random one of an enum of several metadata choices. Obviously I can't do this on a static mint site because you could exploit the contract by passing your own integer via a script directly to the contract. I could make an IPFS folder of pre-generated metadata that is named incrementally as in: 1.json, 2.json, 3.json, etc... I could make the metadata contents a random assortment of a few metadata choices. If I had 3 files 1.json, 2.json, and 3.json, I could make the metadata: 1.json 2.json 2.json 1.json 3.json 2.json 3.json 1.json 1.json 2.json 3.json 2.json 1.json 1.json 1.json 1.json 3.json 2.json 3.json 2.json 2.json 2.json 1.json, and so on... But this isn't ideal because an attacker could just view the metadata files, decide which one the attacker likes and wait for that integer to be next in line for minting. Chainlink VRF is probably the only thing that can actually generate a true random value at mint time that is somewhat hard to attack. But I can't find a recent enough tutorial on how to mint an NFT this way. Is there any tutorial that shows how to mint an NFT that selects the metadata at random at run time? Whether it uses Chainlink or something else?

1 Answer 1


Yes, you are correct that Chainlink VRF is a good solution to generate a random number at mint time for your NFT, and it can help to prevent any potential exploitation of the contract.

Here's example code I got from somewhere, worked for me

pragma solidity ^0.8.0;

import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";

contract MyNFT is VRFConsumerBase {
    uint256 public tokenIdCounter;
    enum MetadataChoices { Choice1, Choice2, Choice3 }

    bytes32 internal keyHash;
    uint256 internal fee;
    uint256 public randomResult;

    mapping(bytes32 => address) public requestIdToSender;
    mapping(bytes32 => uint256) public requestIdToTokenId;
    mapping(uint256 => string) public tokenIdToMetadata;

    event requestedRandomness(bytes32 requestId);

    constructor(address vrfCoordinator, address link, bytes32 _keyHash, uint256 _fee) 
        VRFConsumerBase(vrfCoordinator, link) {
        keyHash = _keyHash;
        fee = _fee;

    function requestRandomNumber(uint256 tokenId) external returns (bytes32) {
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK to fulfill request");
        bytes32 requestId = requestRandomness(keyHash, fee);
        requestIdToSender[requestId] = msg.sender;
        requestIdToTokenId[requestId] = tokenId;
        emit requestedRandomness(requestId);
        return requestId;

    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
        address sender = requestIdToSender[requestId];
        uint256 tokenId = requestIdToTokenId[requestId];
        uint256 index = randomness % metadataChoices.length;
        tokenIdToMetadata[tokenId] = _metadataForChoice(metadataChoices[index]);
        randomResult = randomness;
        _safeMint(sender, tokenId);

  • Awesome thank you for the answer! I will spin this up on a testnet to double check that the code works. Apr 5 at 6:25
  • 1
    Sure and if you have any issues lemme know Apr 6 at 3:56
  • I'm currently getting this error imgur.com/a/DhqegWC. Was metadataChoices supposed to be declared earlier in the contract? Apr 7 at 23:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.