Minting NFTs – How to Randomize Values in a Mintable NFT

erc-721mintnftrandomrandomness

I've read and written simple smart contracts in the past yet I'm relatively new to getting random data in a transaction, which seems to be harder than I've thought, due to the nature of determinism of EVM.

I'm working on a NFT project where there will be several rarity categories of NFTs. The rarity needs to be randomly assigned on mint. Let's keep things simple and for the sake of example assume there will be a total supply of 100 items and 5 of them will be rare.

I've researched this a bit and found the following:

  • Commit and reveal is NOT an option as it's not a lottery. I need everything to be set and frozen on mint.
  • I can't get input from the user as a random: even if I provide a nice UX with random generation at JS, client side random generation is obviously insecure.
  • I can't simply give up on-chain randomness and generate a random rarity on my side:
    1. If I put the NFT data into IPFS, they will be accessible in advance for each NFT, obviously not an option.
    2. If I point metadata URI to some HTTP server that I control, users will need to fully trust me on a fair distribution, which I don't want. I need things to be trustless.
  • I'm not sure about external oracles such as Provable or Chainlink, they seem to use callbacks for random generation, don't know exactly how to proceed that way. It also costs, I'd prefer a free one, though if it's the only option I could pay for them.
  • I've also checked the contract code of LOOT at https://etherscan.io/address/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7#code. It seems to have a random function defined like this:
    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }

It seems to take KECCAK-256 on the input, which is itself just concetenated+packed form of some hardcoded string and token number. I don't think it's safe to from an attacker to get a rare mint at all, but correct me if I'm wrong.

What would be the best way to proceed? I know generating randoms have been asked before, but many of the questions are about either lotteries (that can be revealed in the future) or collections with things being equally rare but just random. Is going with something similar to Loot's contract safe, or is going 2-process with an oracle or commit/reveal the only good way to go?

Best Answer

I've ended up doing a two-phase mint:

  • At first phase the tokens are minted and their block number is stored on chain, but their value/rarity isn't determined.

  • After waiting at least 1 block but not more than 256 blocks, a second call is made to calculate the randomness based on the block hash of the next block after the block that the NFT was first minted.

The downside is that it's two-phase; user needs two transactions each costing some gas and it needs to be done within 256 blocks otherwise the blockhash of N+1 will be zero. (which I disincentivized by defaulting that case to the lowest-rarity (most common) tier in my project)

Related Topic