I am currently re-learning Solidity to sharpen my skills and writing a "WTF Solidity Quick Start" guide for beginners to use (advanced programmers can look for other tutorials). I will update 1-3 lectures every week.
Twitter: @0xAA_Science
Discord: WTF Academy
All code and tutorials are open source on GitHub: github.com/AmazingAng/WTF-Solidity
Many Ethereum applications require the use of random numbers, such as NFT random tokenId selection, blind box drawing, and randomly determining the winner in gamefi battles. However, since all data on Ethereum is public and deterministic, it cannot provide developers with a method of generating random numbers like other programming languages. In this tutorial, we will introduce two methods of on-chain (hash function) and off-chain (Chainlink oracle) random number generation, and use them to create a tokenId random minting NFT.
On-chain Random Number Generation
We can use some on-chain global variables as seeds and use the keccak256() hash function to obtain pseudo-random numbers. This is because the hash function has sensitivity and uniformity, and can generate "apparently" random results. The getRandomOnchain() function below uses the global variables block.timestamp, msg.sender, and blockhash(block.number-1) as seeds to obtain random numbers:
Note: This method is not secure:
- Firstly, variables such as
block.timestamp,msg.sender, andblockhash(block.number-1)are all publicly visible. Users can predict the random number generated by these seeds and select the output they want to execute the smart contract. - Secondly, miners can manipulate
blockhashandblock.timestampto generate a random number that suits their interests.
However, this method is the most convenient on-chain random number generation method, and many project parties rely on it to generate insecure random numbers, including well-known projects such as meebits and loots. Of course, all these projects have been attacked: attackers can forge any rare NFT they want, instead of randomly drawing them.
Off-chain random number generation
We can generate random numbers off-chain and upload them to the chain through oracles. Chainlink provides a VRF (Verifiable Random Function) service, and on-chain developers can pay the LINK token to obtain a random number. Chainlink VRF has two versions. Since the second version requires registration on the official website and prepaid fees, and the usage is similar, only the first version VRF v1 is introduced here.
Steps to use Chainlink VRF

We will use a simple contract to introduce the steps to use Chainlink VRF. The RandomNumberConsumer contract can request a random number from the VRF and store it in the state variable randomResult.
1. The user contract inherits from VRFConsumerBase and transfers the LINK token
To use VRF to obtain a random number, the contract needs to inherit the VRFConsumerBase contract and initialize the VRF Coordinator address, LINK token address, unique identifier Key Hash, and usage fee fee in the constructor.
Note: Different chains correspond to different parameters, please refer to here to find out.
In the tutorial, we use the Rinkeby testnet. After deploying the contract, users need to transfer some LINK tokens to the contract. Testnet LINK tokens can be obtained from the LINK faucet.
2. User requests random number through contract
Users can call requestRandomness() inherited from the VRFConsumerBase contract to request a random number and receive a request identifier requestId. This request will be passed on to the VRF contract.
-
The
Chainlinknode generates a random number and a digital signature off-chain and sends them to theVRFcontract. -
The
VRFcontract verifies the validity of the signature. -
The user contract receives and uses the random number.
After verifying the validity of the signature in the VRF contract, the fallback function fulfillRandomness() of the user contract will be automatically called, and the off-chain generated random number will be sent over. The logic of consuming the random number should be implemented in this function.
Note: The requestRandomness() function is called by the user to request a random number and the fallback function fulfillRandomness() is called when the VRF contract returns the random number are two separate transactions, with the user contract and the VRF contract being the callers, respectively. The latter will be a few minutes later than the former (with different chain delays).
tokenId Randomly Minted NFT
In this section, we will use on-chain and off-chain random numbers to create a tokenId randomly minted NFT. The Random contract inherits from both the ERC721 and VRFConsumerBase contracts.
State Variables
NFTrelatedtotalSupply: Total supply ofNFT.ids: Array used for calculatingtokenIds that can beminted, seepickRandomUniqueId()function.mintCount: Number of NFTs that have beenminted.
Chainlink VRFrelatedkeyHash: Unique identifier forVRF.fee:VRFfee.requestToSender: Records the user address that applied forVRFfor minting.
Constructor
Initialize the relevant variables of the inherited VRFConsumerBase and ERC721 contracts.
Other Functions
In addition to the constructor function, the contract defines 5 other functions:
-
pickRandomUniqueId(): takes in a random number and returns atokenIdthat can be used for minting. -
getRandomOnchain(): returns an on-chain random number (insecure). -
mintRandomOnchain(): mints an NFT using an on-chain random number, and callsgetRandomOnchain()andpickRandomUniqueId(). -
mintRandomVRF(): requests a random number fromChainlink VRFto mint an NFT. Since the logic for minting with a random number is in the callback functionfulfillRandomness(), which is called by theVRFcontract, not the user minting the NFT, the function here must use therequestToSenderstate variable to record the user address corresponding to theVRFrequest identifier. -
fulfillRandomness(): the callback function forVRF, which is automatically called by theVRFcontract after verifying the authenticity of the random number. It uses the returned off-chain random number to mint an NFT.
remix Verification
1. Deploy the Random contract on the Rinkeby testnet

2. Get LINK and ETH on the Rinkeby testnet using Chainlink faucet

3. Transfer LINK tokens into the Random contract
After the contract is deployed, copy the contract address, and transfer LINK to the contract address just as you would for a normal transfer.

4. Mint NFTs using on-chain random numbers
In the remix interface, click on the orange function mintRandomOnchain on the left side

Metamask to start minting the transaction using on-chain random numbers.

5. Mint NFTs using Chainlink VRF off-chain random numbers
Similarly, in the remix interface, click on the orange function mintRandomVRF on the left and click confirm in the pop-up little fox wallet. The transaction of minting an NFT using Chainlink VRF off-chain random number has started.
Note: when using VRF to mint NFT, initiating the transaction and the success of minting is not in the same block.


6. Verify that the NFT has been minted
From the above screenshots, it can be seen that in this example, the NFT with tokenId=87 has been randomly minted on-chain, and the NFT with tokenId=77 has been minted using VRF.
Conclusion
Generating a random number in Solidity is not as straightforward as in other programming languages. In this tutorial, we introduced two methods of generating random numbers on-chain (using hash functions) and off-chain (Chainlink oracle), and used them to create an NFT with a randomly assigned tokenId. Both methods have their advantages and disadvantages: using on-chain random numbers is efficient but insecure while generating off-chain random numbers relies on third-party Oracle services, which is relatively safe but not as easy and economical. Project teams should choose the appropriate method according to their specific business needs.
Apart from these methods, there are other organizations that are trying new ways of RNG (Random Number Generation), such as randao, which proposes to provide an on-chain and true randomness service in a DAO pattern.