Skip to main content

Request Workflow

The flow

Request flow

Step 0: Consumers have to first create and fund the subscription account

Step 1: To initiate a random number request, the consumer contract calls the requestRandomWords() method in the VRF Coordinator contract.

Step 2: The VRF Coordinator contract received the request from the consumer contract, then calculates the requestID and preSeed by using the parameters in the request, an event with parameters will be broadcasted and logged to the chain.

Step 3: Once the VRF off-chain received the event, it will generate the proof with its private key and then send the proof back to the VRF Coordinator contract.

Step 4: VRF Coordinator contract will verify the proof, if the proof is valid, the contract will use gamma(an encrypted param in the proof) as the seed to generate the random number.

Step 5: Finally, the VRF Coordinator contract calls the fulfillment method in the consumer contract(e.g. fulfillRandomWords) to complete the request.

Create and fund a subscription account

Before consumer can request for random numbers, they will have to create and fund a subscription account. The fee required to generate these random numbers will be deducted from the subscription account.

Create a subscription account

Create a subscription account by calling the createSubscription() method in VRF Coordinator contract. Once the account is created, a subscription ID is created.

 it('CreateSubscription', async () => {
const testVRFCoordinatorContract = await ethers.getContractFactory(
'VRFCoordinatorContract',
)
const contract = await testVRFCoordinatorContract.attach(
VRF_CONTRACT_ADDRESS,
)
try {
const obj = await contract.createSubscription()
assert.isObject(obj, 'createSubscription failed')
ethers.provider.on(
{
address: VRF_CONTRACT_ADDRESS,
topics: [
'0x464722b4166576d3dcbba877b999bc35cf911f4eaf434b7eba68fa113951d0bf',
],
},
(log) => {
const subId = BigNumber.from(log.topics[1]).toNumber()
const caller = BigNumber.from(log.data).toHexString()
console.log(`subId: ${subId},caller: ${caller}`)
},
)
} catch (e) {
assert.fail('createSubscription failed')
}
})

Fund the subsciption account

Call the method deposit(subId, amount) method in VRF Coordinator contract to fund the subscription account

danger

Please make sure the subscription ID (subId) is correct before deposit!

info

Please be aware that at the moment, we limit each subscription account to have a maximum of 20 BNB balance. Any deposit that will result in a balance > 20BNB will be reverted.

it('Deposit', async () => {
const testVRFCoordinatorContract = await ethers.getContractFactory(
'VRFCoordinatorContract',
)
const contract = await testVRFCoordinatorContract.attach(
VRF_CONTRACT_ADDRESS,
)
try {
const oldBalance = await ethers.provider.getBalance(VRF_CONTRACT_ADDRESS)
const oldBalanceStr = BigNumber.from(oldBalance).toString()
// the params of the function deposit includes subId, type: uint64
await contract.deposit(1, {
value: ethers.utils.parseEther('2'),
gasLimit: 500000,
})
await new Promise((resolve) => setTimeout(resolve, 3000))
const newBalance = await ethers.provider.getBalance(VRF_CONTRACT_ADDRESS)
const newBalanceStr = BigNumber.from(newBalance).toString()
assert.equal(
Number(newBalanceStr),
Number(oldBalanceStr) + 2 * 1e18,
'deposit failed',
)
} catch (e) {
assert.fail('deposit failed')
}
})

Prepare the consumer contract

The consumer contract is a contract prepared by the consumer to receive the random numbers. It needs to inherit and override the fulfillRandomWords method of the abstract contract VRFConsumerBase.sol. A simple consumer contract as follows.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./VRFConsumerBase.sol";
import "../interfaces/VRFCoordinatorInterface.sol";

contract VRFConsumerContract is VRFConsumerBase {
VRFCoordinatorInterface private COORDINATOR;
bytes32 private keyHash;
uint64 private subId;
uint16 private requestConfirmations;
uint32 private callbackGasLimit;
uint32 private numWords;
uint256 public requestIdReceive;
uint256[] public randomWordsReceive;
constructor(
uint64 _subId,
bytes32 _keyHash,
uint32 _callbackGasLimit,
uint16 _requestConfirmations,
uint32 _numWords,
address vrfCoordinator) VRFConsumerBase(vrfCoordinator) {
COORDINATOR = VRFCoordinatorInterface(vrfCoordinator);
subId = _subId;
keyHash = _keyHash;
callbackGasLimit = _callbackGasLimit;
requestConfirmations = _requestConfirmations;
numWords = _numWords;
}

function requestRandomWords() external {
// Will revert if subscription is not set and funded.
requestIdReceive = COORDINATOR.requestRandomWords(
keyHash,
subId,
requestConfirmations,
callbackGasLimit,
numWords
);
}

function fulfillRandomWords(
uint256, /* requestId */
uint256[] memory randomWords
) internal override {
randomWordsReceive = randomWords;
}
}

Parameters in the consumer contract

  • _subId:Subscription Account id
  • _keyHash:The keyHash of the node
  • _callbackGasLimit: Depends on the number of requested values that you want sent to the fulfillRandomWords() function,For example, for a single request for 2 random numbers, 100000 is sufficient
  • _requestConfirmations: Minimum number of block confirmations(3<= _requestConfirmations <=200)
  • _numWords:The number of random numbers you want to return in a single request( max 500)

Add consumer contract to the subscription account

it('AddConsumer', async () => {
const testVRFCoordinatorContract = await ethers.getContractFactory(
'VRFCoordinatorContract',
)
const contract = await testVRFCoordinatorContract.attach(
VRF_CONTRACT_ADDRESS,
)
try {
const subInfo = await contract.getSubscription(1)
assert.isArray(subInfo, 'getSubscription failed')
await contract.addConsumer(1, CONSUMER_CONTRACT_ADDRESS)
await new Promise((resolve) => setTimeout(resolve, 3000))
const newSubInfo = await contract.getSubscription(1)
assert.isArray(subInfo, 'getSubscription failed')
if (
subInfo.consumers.length !== newSubInfo.consumers.length &&
subInfo.consumers.length !== newSubInfo.consumers.length - 1
) {
assert.fail('addConsumer failed')
}
} catch (e) {
assert.fail('addConsumer failed')
}
})

Check subscription account information

  1. Prepare your subscription account id
  2. Call getSubscription(subId) in the VRFCoordinatorContract to get the current subscription account information
  3. Verify if the subscription account information is correct
it('GetSubscription', async () => {
const testVRFCoordinatorContract = await ethers.getContractFactory(
'VRFCoordinatorContract',
)
const contract = await testVRFCoordinatorContract.attach(
VRF_CONTRACT_ADDRESS,
)
try {
const subInfo = await contract.getSubscription(1)
assert.isArray(subInfo, 'getSubscription failed')
assert.exists(subInfo.owner, 'getSubscription successfully')
console.log(subInfo)
} catch (e) {
assert.fail('getSubscription failed')
}
})

Receive the random numbers

Once all the above steps are completed, you can initiate a request for random numbers using requestRandomWords() and wait for it to be sent back in the randomWordsReceive array.

Please note that if you request n random numbers in your request, this randomWordsReceive will be with length n.