Skip to main content

Example Use Cases

Band VRF provides deterministic pre-commitments for low entropy inputs, which must resist brute-force pre-image attacks. In addition, the VRF can be used as defense against offline enumeration attacks (such as dictionary attacks) on data stored in hash-based data structures. Therefore, it can be used as a secure source of on-chain randomness. The Band VRF use cases are outlined below.

Use cases

We separate the use cases into four categories based on the behaviors of the VRF users/consumers.

  1. One-time-use: Consumers only request random data once in their product lifetime, typically when they initiate their contracts.

    • NFT minting - in the case where a single random seed is used to generate the entire collection.
  2. Batch-use: Consumers request random data multiple times but with a countable number of requests for the entire life of their product.

    • NFT minting - in the case where every single ID will be minted one by one. Therefore, whenever the end-user tries to mint an NFT, the VRF request is created to resolve the minting. This process will continue until the entire collection is minted.
  3. Interval-use: Consumers set their specific intervals to request random data from our VRF protocol. This process will continue indefinitely since some parts of their products rely on a trusted source of randomness.

    • Lottery dApps (predetermined start-end, calculatable start-end)
    • NFT minting with a specific minting interval
  4. Continuous-use: Consumers use randomness as parts of their product with no specific interval. Therefore, they can request random data at any time based on the internal logic of their contracts/system.

    • Lottery dApps (no predetermined interval, unable to calculate future start-end)
    • On-chain games
    • NFT on-demand minting

Lottery Example (Continuous-use)

This on-chain lottery dApp is an example of a continuous-use VRF, and it satisfies the requirements below.

Requirements:

  • Only the owner can set the minimum price and the round's duration.
  • Only the owner can start a new round.
  • The owner determined the seed of the started round.
  • Anyone can buy lotteries during a started round.
  • Anyone can request to resolve the current round if it has ended.
  • Only the VRFProvider contract can resolve the resolving round.
pragma solidity ^0.8.17;

interface IVRFConsumer {
/// @dev The function is called by the VRF provider in order to deliver results to the consumer.
/// @param seed Any string that used to initialize the randomizer.
/// @param time Timestamp where the random data was created.
/// @param result A random bytes for given seed anfd time.
function consume(
string calldata seed,
uint64 time,
bytes32 result
) external;
}

interface IVRFProvider {
/// @dev The function for consumers who want random data.
/// Consumers can simply make requests to get random data back later.
/// @param seed Any string that used to initialize the randomizer.
function requestRandomData(string calldata seed) external payable;
}

contract SimpleLottery is IVRFConsumer {

event Buy(address buyer, uint256 buyerIndex, uint256 roundNumber, uint256 buyPrice);
event StartRound(uint256 roundNumber, string seed);
event ResolvingRound(uint256 roundNumber, string seed);
event RoundResolved(uint256 roundNumber, string seed, bytes32 result);

address public owner;

uint256 public minLotteryPrice;
uint256 public roundDuration;
uint256 public roundCount;
bool public isResolvingCurrentRound;

IVRFProvider public provider;

struct Round {
uint256 startBlock;
uint256 endBlock;
string seedOfRound;
address[] buyers;
}

mapping(uint256 => Round) public rounds;

constructor(IVRFProvider _provider, uint256 _minLotteryPrice, uint256 _roundDuration) {
provider = _provider;
minLotteryPrice = _minLotteryPrice;
roundDuration = _roundDuration;
owner = msg.sender;
}

function isCurrentRoundStart() public view returns(bool) {
return rounds[roundCount].startBlock > 0;
}

function currentRoundBlocksRemaining() public view returns(uint256) {
Round memory currentRound = rounds[roundCount];
if (block.number > currentRound.endBlock) {
return 0;
}
return currentRound.endBlock - block.number;
}

function setMinLotteryPrice(uint256 _minLotteryPrice) external {
require(msg.sender == owner, "SimpleLottery: not the owner");
require(!isCurrentRoundStart(), "SimpleLottery: this round is in progress");

minLotteryPrice = _minLotteryPrice;
}

function setRoundDuration(uint256 _roundDuration) external {
require(msg.sender == owner, "SimpleLottery: not the owner");
require(!isCurrentRoundStart(), "SimpleLottery: this round is in progress");

roundDuration = _roundDuration;
}

function startANewRound(string memory roundSeed) external {
require(msg.sender == owner, "SimpleLottery: not the owner");
require(!isCurrentRoundStart(), "SimpleLottery: this round is in progress");

Round memory currentRound = rounds[roundCount];

currentRound.seedOfRound = roundSeed;
currentRound.startBlock = block.number;
currentRound.endBlock = currentRound.startBlock + roundDuration;

rounds[roundCount] = currentRound;
emit StartRound(roundCount, roundSeed);
}

function buy() external payable {
require(currentRoundBlocksRemaining() > 0, "SimpleLottery: this round is not in progress");
require(msg.value >= minLotteryPrice, "SimpleLottery: given price is too low");

uint256 currentBuyerIndex = rounds[roundCount].buyers.length;
emit Buy(msg.sender, currentBuyerIndex, roundCount, msg.value);

rounds[roundCount].buyers.push(msg.sender);
}

function resolveCurrentRound() external {
require(isCurrentRoundStart(), "SimpleLottery: this round is not started yet");
require(currentRoundBlocksRemaining() == 0, "SimpleLottery: this round has not ended yet");
require(!isResolvingCurrentRound, "SimpleLottery: round is resolving");

Round memory currentRound = rounds[roundCount];
if (currentRound.buyers.length > 0) {
isResolvingCurrentRound = true;

provider.requestRandomData{value: 0}(currentRound.seedOfRound);
emit ResolvingRound(roundCount, currentRound.seedOfRound);
} else {
emit RoundResolved(roundCount, currentRound.seedOfRound, bytes32(0));

roundCount += 1;
isResolvingCurrentRound = false;
}
}

function consume(string calldata seed, uint64 time, bytes32 result) external override {
require(msg.sender == address(provider), "Caller is not the provider");
require(isResolvingCurrentRound, "SimpleLottery: round is not resolving");

Round memory currentRound = rounds[roundCount];
address winner = currentRound.buyers[uint256(result) % currentRound.buyers.length];

emit RoundResolved(roundCount, seed, result);

roundCount += 1;
isResolvingCurrentRound = false;

winner.call{value: address(this).balance}("");
}
}

We have deployed the reference contracts to the Goerli testnet here.

ContractAddress
Bridge0xD291A502e3ca4Bb13E09892e57d8Ff0271Bd198A
VRFProvider0xF1F3554b6f46D8f172c89836FBeD1ea8551eabad
VRFLens0x6e876b4Ed458af275Eb049a3f89BF0909618d154
SimpleLottery0xCD3528283aA330003E50350134a48d1920BA70A0

NFT Minting Example (Batch-use)

This NFT is an example of a batch-use VRF, and it satisfies the requirements below.

Requirements:

  • The max supply is set once at the time the contract is deployed.
  • Anyone can call mintWithVRF to start minting an NFT for themself.
  • An actual minting is done when the VRFprovider resolves the token id for the minter/receiver.
pragma solidity ^0.8.17;

import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}

interface IVRFConsumer {
/// @dev The function is called by the VRF provider in order to deliver results to the consumer.
/// @param seed Any string that used to initialize the randomizer.
/// @param time Timestamp where the random data was created.
/// @param result A random bytes for given seed anfd time.
function consume(
string calldata seed,
uint64 time,
bytes32 result
) external;
}

interface IVRFProvider {
/// @dev The function for consumers who want random data.
/// Consumers can simply make requests to get random data back later.
/// @param seed Any string that used to initialize the randomizer.
function requestRandomData(string calldata seed) external payable;
}

contract ExampleNFT is ERC721Enumerable, IVRFConsumer {
using Strings for uint256;

IVRFProvider public immutable provider;
uint256 public immutable maxSupply;

uint256 public mintRequestCount = 0;
uint256 public mintResolveCount = 0;

mapping(uint256 => uint256) public tokenMintingLogs;
mapping(string => address) public tokenSeedToMinter;

constructor(IVRFProvider _provider, uint256 _maxSupply) ERC721("ExampleNFT", "ENFT") {
provider = _provider;
maxSupply = _maxSupply;
}

function mintWithVRF() external {
require(mintRequestCount < maxSupply, "Reach max supply");
string memory clientSeed = string(abi.encodePacked("ExampleNFT-", mintRequestCount.toString()));
tokenSeedToMinter[clientSeed] = msg.sender;

mintRequestCount++;

provider.requestRandomData{value: 0}(clientSeed);
}

function consume(string calldata seed, uint64 time, bytes32 result) external override {
require(msg.sender == address(provider), "Caller is not the provider");

address _receiver = tokenSeedToMinter[seed];
uint256 index = uint256(result) % (maxSupply - mintResolveCount);

uint256 tokenID = tokenMintingLogs[index];
if (tokenID == 0) {
tokenID = index;
}

mintResolveCount++;
tokenMintingLogs[index] = maxSupply - mintResolveCount;

_safeMint(_receiver, tokenID);
}
}

We have deployed the reference contracts to the Goerli testnet here.

ContractAddress
Bridge0xD291A502e3ca4Bb13E09892e57d8Ff0271Bd198A
VRFProvider0xF1F3554b6f46D8f172c89836FBeD1ea8551eabad
VRFLens0x6e876b4Ed458af275Eb049a3f89BF0909618d154
NFTBatchMinting0x0b590C537608d121F8e46c2b366f5d22EC942c0f