AvaCloud VRF Quickstart Guide

Congratulations! You have just completed the AvaCloud VRF onboarding flow. This quickstart guide assumes you have completed the VRF setup process successfully on your L1 and have access to an active VRF Proxy dashboard.

AvaCloud VRF is a service that provides on-chain random values to Avalanche L1s deployed via AvaCloud. This service leverages Chainlink VRF on the Avalanche C-Chain to fetch the random values and pass back to the L1. For more details on the design and architecture, please see the contracts.

Prerequisite

This guide uses Foundry for smart contract development. If you do not have Foundry, run the following command:

curl -L https://foundry.paradigm.xyz | bash

To ensure proper installation, run:

forge --version
cast --version

Integration Steps

Once the VRF Proxy is deployed, the steps to integrate with a solidity smart contract are simple.

  1. Create a new forge project. This will create a new repository. After running, you should see the following files in the directory.
forge init avacloud-vrf
cd avacloud-vrf
ls
README.md foundry.toml lib script src test
  1. Install dependencies.
forge install NIMA-Enterprises/subnet-vrf
forge install smartcontractkit/chainlink

Add the following to your foundry.toml file under the [profile.default] header

remappings = ["@chainlink/=lib/chainlink/", "@vrf/=lib/subnet-vrf/src"]
  1. Create a new smart contract file in /src and import the required dependencies. Create a default constructor to accept the vrfProxyAddress found in the AvaCloud portal.
Import Required Dependencies and Create Constructor
1// SPDX-License-Identifier: UNLICENSED
2
3pragma solidity 0.8.25;
4
5import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
6import {IVRFProxy} from "@vrf/interfaces/IVRFProxy.sol";
7
8contract AvaCloudVRFConsumer is VRFConsumerBaseV2 {
9 IVRFProxy private proxy;
10
11 constructor(address vrfProxyAddress) VRFConsumerBaseV2(vrfProxyAddress) {
12 proxy = IVRFProxy(vrfProxyAddress);
13 }
14}
  1. Implement contract. By inheriting VRFConsumerBaseV2, you must implement fulfillRandomWords() as a callback once a VRF request is returned. Our implementation allows users to guess a random number between 1 - 10, and rewards them if they guess correctly.
Constants
1struct RandomNumberGuess {
2 address requestor;
3 uint256 guess;
4 bool isResolved;
5}
6
7IVRFProxy private proxy;
8mapping(uint256 vrfRequestId => RandomNumberGuess guess) public guesses;
9// if requesting more than one random number, value of mapping should be array
10mapping(uint256 vrfRequestId => uint256 randomNum) public returnedNumber;
11mapping(address guesser => uint256 amountToClaim) public rewards;
12
13uint256 public constant REWARD_AMOUNT = 0.05 ether;
14
15event Payout(address indexed guesser, uint256 reward);
16event Fund(address indexed funder, uint256 total);
17event RandomNumberGuessed(
18 address indexed guesser,
19 uint256 guess,
20 uint256 indexed vrfRequestID
21);
22event RewardsClaimed(address owner, uint256 reward);
23
24error InvalidDataLength();
25error InvalidRequestID();
26error RequestAlreadyResolved();
27error NoRewardsToClaim();
Helper Functions
1receive() external payable {
2 emit Fund(msg.sender, msg.value);
3}
4
5function proxyAddress() public view returns (address) {
6 return address(proxy);
7}
8
9function claimRewards() external {
10 uint256 reward = rewards[msg.sender];
11 if (reward <= 0) revert NoRewardsToClaim();
12
13 rewards[msg.sender] = 0;
14 (bool success, ) = msg.sender.call{value: reward}("");
15 require(success, "Transfer failed");
16
17 emit RewardsClaimed(msg.sender, reward);
18}
Guess Function: to fetch a random number, call proxy.requestRandomWords()
1function guessRandomNumber(
2 uint256 guess
3) public returns (uint256 _requestId) {
4 // blockDelay and callbackGasLimit are recommended values
5 uint16 blockDelay = 1;
6 uint32 callbackGasLimit = 200_000;
7 uint32 numberOfRandomValues = 1;
8
9 // get the current nonce and increment
10 uint256 requestId = proxy.requestNonce() + 1;
11 guesses[requestId] = RandomNumberGuess(msg.sender, guess, false);
12 proxy.requestRandomWords(
13 blockDelay,
14 callbackGasLimit,
15 numberOfRandomValues
16 );
17 emit RandomNumberGuessed(msg.sender, guess, requestId);
18 return requestId;
19}
Callback Function
1function fulfillRandomWords(
2 uint256 _requestId,
3 uint256[] memory _randomWords
4) internal override {
5 if (_randomWords.length != 1) revert InvalidDataLength();
6 RandomNumberGuess storage guess = guesses[_requestId];
7 if (guess.requestor == address(0)) revert InvalidRequestID();
8 if (guess.isResolved) revert RequestAlreadyResolved();
9 guess.isResolved = true;
10
11 // Set the range of responses to be 1 - 10
12 uint256 result = (_randomWords[0] % 10) + 1;
13 returnedNumber[_requestId] = result;
14 if (result == guess.guess) {
15 rewards[guess.requestor] += REWARD_AMOUNT;
16 }
17}
  1. Deploy contract: take note of the contract address
forge create --rpc-url RPC_URL \
--private-key PRIVATE_KEY \
src/AvaCloudVRFConsumer.sol:AvaCloudVRFConsumer \
--constructor-args {ADDRESS}
  1. Add the contract address to the Consumer Allowlist in the AvaCloud Portal VRF module

  1. Test the contract. The last value is the guess, for this demo we are guessing 5.
cast send --rpc-url RPC_URL \
--private-key PRIVATE_KEY \
CONTRACT_ADDRESS "guessRandomNumber(uint256)" 5

To get the requestID, look in the event logs for your smart contract address (ours is 0xc615f4614056bc15744db453d119134ee48e1cde). Since the last value in the topics list is 0x0...02, the requestID is 2

"address":"0xc615f4614056bc15744db453d119134ee48e1cde",
"topics":[
"0x1d9111435f4b854f073bc412802d9927efaba88c5d4f981bb66464a1ea11f045",
"0x000000000000000000000000b4ca6c121d6287af7ac7cb62ae33d2b054b9fc44",
"0x0000000000000000000000000000000000000000000000000000000000000002" # REQUEST ID IN HEX
],
"data":"0x0000000000000000000000000000000000000000000000000000000000000005"

Then, query the guesses mapping to see if your request was successfully resolved.

cast call SMART_CONTRACT_ADDRESS "guesses(uint256)(address,uint256,bool)" REQUEST_ID --rpc-url RPC_URL
0xB4cA6C121D6287af7ac7cb62Ae33d2b054b9FC44
5
true

Since the last value is true, we know our request was resolved. Finally, we can check to see what random valued was returned and if we guessed correctly.

cast call SMART_CONTRACT_ADDRESS "returnedNumber(uint256)(uint256)" 2 --rpc-url RPC_URL
7

Unfortunately, we did not guess correctly, but we now know our contract successfully returns a random value from C-Chain!

Congratulations, we have now successfully created and deployed a contract to use random values! Now you can extend this functionality in your own smart contracts.

Full Contract Implementation

AvaCloudVRFConsumer.sol
1// SPDX-License-Identifier: UNLICENSED
2
3pragma solidity 0.8.25;
4
5import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
6import {IVRFProxy} from "@vrf/interfaces/IVRFProxy.sol";
7
8contract AvaCloudVRFConsumer is VRFConsumerBaseV2 {
9 struct RandomNumberGuess {
10 address requestor;
11 uint256 guess;
12 bool isResolved;
13 }
14
15 IVRFProxy private proxy;
16 mapping(uint256 vrfRequestId => RandomNumberGuess guess) public guesses;
17 // if requesting more than one random number, value of mapping should be array
18 mapping(uint256 vrfRequestId => uint256 randomNum) public returnedNumber;
19 mapping(address guesser => uint256 amountToClaim) public rewards;
20
21 uint256 public constant REWARD_AMOUNT = 0.05 ether;
22
23 event Payout(address indexed guesser, uint256 reward);
24 event Fund(address indexed funder, uint256 total);
25 event RandomNumberGuessed(
26 address indexed guesser,
27 uint256 guess,
28 uint256 indexed vrfRequestID
29 );
30 event RewardsClaimed(address owner, uint256 reward);
31
32 error InvalidDataLength();
33 error InvalidRequestID();
34 error RequestAlreadyResolved();
35 error NoRewardsToClaim();
36
37 constructor(address vrfProxyAddress) VRFConsumerBaseV2(vrfProxyAddress) {
38 proxy = IVRFProxy(vrfProxyAddress);
39 }
40
41 receive() external payable {
42 emit Fund(msg.sender, msg.value);
43 }
44
45 function proxyAddress() public view returns (address) {
46 return address(proxy);
47 }
48
49 function claimRewards() external {
50 uint256 reward = rewards[msg.sender];
51 if (reward <= 0) revert NoRewardsToClaim();
52
53 rewards[msg.sender] = 0;
54 (bool success, ) = msg.sender.call{value: reward}("");
55 require(success, "Transfer failed");
56
57 emit RewardsClaimed(msg.sender, reward);
58 }
59
60 function guessRandomNumber(
61 uint256 guess
62 ) public returns (uint256 _requestId) {
63 // blockDelay and callbackGasLimit are recommended values
64 uint16 blockDelay = 1;
65 uint32 callbackGasLimit = 200_000;
66 uint32 numberOfRandomValues = 1;
67
68 // get the current nonce and increment
69 uint256 requestId = proxy.requestNonce() + 1;
70 guesses[requestId] = RandomNumberGuess(msg.sender, guess, false);
71 proxy.requestRandomWords(
72 blockDelay,
73 callbackGasLimit,
74 numberOfRandomValues
75 );
76 emit RandomNumberGuessed(msg.sender, guess, requestId);
77 return requestId;
78 }
79
80 function fulfillRandomWords(
81 uint256 _requestId,
82 uint256[] memory _randomWords
83 ) internal override {
84 if (_randomWords.length != 1) revert InvalidDataLength();
85 RandomNumberGuess storage guess = guesses[_requestId];
86 if (guess.requestor == address(0)) revert InvalidRequestID();
87 if (guess.isResolved) revert RequestAlreadyResolved();
88 guess.isResolved = true;
89
90 // Set the range of responses to be 1 - 10
91 uint256 result = (_randomWords[0] % 10) + 1;
92 returnedNumber[_requestId] = result;
93 if (result == guess.guess) {
94 rewards[guess.requestor] += REWARD_AMOUNT;
95 }
96 }
97}

For any additional questions, please view our other knowledge base articles or contact a support team member via the chat button. Examples are for illustrative purposes only.

Learn More About AvaCloud | Download Case Studies | Schedule an AvaCloud Demo