Back to QuickStarts

Pass VRF Costs to the End Users

Identify the cost of specific VRF requests in order to pass this cost to the end users of your application.

Objective

The VRF direct funding method calculates costs at request time. In this tutorial, you will use VRF direct funding and map VRF request IDs to their corresponding responses from the VRF service, along with the cost of each request.

Before you begin

Before you start this tutorial, complete the following items:

  • Install Foundry.

    1. Open a Terminal window and run:
      curl -L https://foundry.paradigm.xyz | bash
      
    2. Follow the instructions in your terminal to add foundryup to your CLI.
    3. Run foundryup. After successful installation, you should get output that contains a Foundry banner. After that, you should see foundryup installing forge and other packages:
      foundryup: installing foundry (version nightly, tag nightly-74c03182d41c75c40e5a1c398aca9400305ff678)
      foundryup: downloading latest forge, cast, anvil, and chisel
      ########################################################################################################## 100.0%
      foundryup: downloading manpages
      ########################################################################################################## 100.0%
      foundryup: installed - forge 0.2.0 (74c0318 2023-09-14T00:27:34.321593000Z)
      foundryup: installed - cast 0.2.0 (74c0318 2023-09-14T00:27:34.059889000Z)
      foundryup: installed - anvil 0.2.0 (74c0318 2023-09-14T00:27:34.192518000Z)
      foundryup: installed - chisel 0.2.0 (74c0318 2023-09-14T00:27:33.928662000Z)
      foundryup: done!
      
  • Install make if you do not already have it.

    1. The Xcode command line tools include make. If you've previously installed Xcode, skip to step 2 to verify your installation. Otherwise, open a Terminal window and run:
      xcode-select --install
      
      Alternatively, if you prefer to use Homebrew, be aware that GNU make is installed as gmake.
    2. Verify your make installation:
      make
      
      If make is installed successfully, you will get the following output:
      make: *** No targets specified or no makefile found. Stop.
      
  • Run git --version to check your git installation. You should see an output similar to git version x.x.x. If not, install git.

  • Get your Etherscan API key for contract verification. If you do not already have one:

  • Create an account with Infura or Alchemy to use as an RPC endpoint if you do not already have one. Alternatively, you can use your own testnet clients.

Steps to implement

1 Clone the example repo

Clone the vrf-direct-funding-example repo:

git clone [email protected]:smartcontractkit/vrf-direct-funding-example.git

This gives you an existing Foundry project to configure and use.

2 Configure the Foundry project

Foundry is an Ethereum application development toolkit that is used here to configure and deploy the contract. You need the following information:

  1. Create an .env file at the root of the vrf-direct-funding-example/ directory:

    # Network RPCs
    export RPC_URL=
    
    # Private key for contract deployment
    export PRIVATE_KEY=
    
    # Explorer API key used to verify contracts
    export ETHERSCAN_API_KEY=
    
  2. In your .env file, fill in these values:

    ParameterDescriptionExample
    RPC_URLThe RPC URL for the network you want to deploy to.https://sepolia.infura.io/v3/your-api-key
    PRIVATE_KEYThe private key of the account you want to deploy from.
    Add 0x before your key.
    0xabc123abc123abc123abc123abc123...
    EXPLORER_KEYThe API key for Etherscan needed for contract verification.ABC123ABC123ABC123ABC123ABC123ABC1
3 Install dependencies

At the root of the vrf-direct-funding-example/ directory, run:

make install

This command will install the project dependencies, which include chainlink and openzepplin-contracts for secure smart contract development.

4 Deploy the contract

Deploy the contract

Foundry includes Forge, an Ethereum testing framework. The project's Makefile helps you invoke Forge with simple commands to start the deployment script.

At the root of the vrf-direct-funding-example/ directory, run this command to deploy the contract:

make deploy
5 Run the example

Test the contract as the end-user.

  1. Open the LINK Token Contract write functions for your network in an explorer. For example, on Ethereum Sepolia you can open the 0x779877A7B0D9E8603169DdbD7836e478b4624789 contract.

  2. Connect your wallet to the block explorer (e.g. Etherscan for Ethereum Sepolia) so you can run write functions.

    Connect wallet to the block explorer
  3. Approve the deployed contract to spend LINK. Run the approve function with your deployed contract address and 5000000000000000000 Juels (5 LINK) as variables. Click Write to run the function. MetaMask asks you to approve the transaction.

    Approve LINK spend
  4. Open your deployed contract in the block explorer.

  5. On the Contract tab, open the Write Contract functions list for your deployed contract.

  6. Again, connect your wallet to the block explorer so you can run write functions on the deployed contract.

    Connect to the block explorer while viewing your deployed contract
  7. In the requestRandomWords function, click Write to initiate a request for randomness as an end-user. Confirm the transaction in MetaMask.

    Request randomness with the Write button under requestRandomWords
  8. If the transaction is successful, you will see a View your transaction button.

  9. On the Read Contract tab for your deployed contract, view and copy lastRequestId value.

  10. Use this value to find the request status and view the random value. Expand getRequestStatus, enter the _requestId, and click Query. The randomWords value displays in the response below the Query button.

    View the request status with your lastRequestId value

Code example

This section explains how the VRFDirectFundingConsumer.sol contract maps each request to its cost.

This RequestStatus struct is used to map attributes of each VRF request to its request ID, including its cost, its fulfillment status, and the random values generated by the VRF service in response to the request:

struct RequestStatus {
        uint256 paid;
        bool fulfilled;
        uint256[] randomWords;
    }

mapping(uint256 => RequestStatus) public s_requests;

The requestRandomWords function calls the calculateRequestPrice function on the VRF wrapper contract to calculate the request cost upfront, and then transfers enough LINK to the VRF wrapper to cover the request. After sending the request, the requestRandomWords function stores the RequestStatus with the cost and other request attributes, and emits a RequestSent event.

function requestRandomWords() external returns (uint256) {
   // Calculate the price required for the VRF request based on the configured parameters.
   uint256 requestPrice = calculateRequestPrice();

   // Transfer the required LINK tokens from the caller's address to this contract.
   transferLinkFromUser(requestPrice);

   // Send the randomness request to the VRF Wrapper contract and retrieve the request ID and the paid price.
   bytes memory extraArgs = VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: false}));
   (uint256 requestId, uint256 reqPrice) = requestRandomness(
      requestConfig.callbackGasLimit, requestConfig.requestConfirmations, requestConfig.numWords, extraArgs
   );
   // Record the request details.
   s_requests[requestId] = RequestStatus({paid: reqPrice, randomWords: new uint256[](0), fulfilled: false});
   requestIds.push(requestId);
   lastRequestId = requestId;

   // Emit an event indicating that a request has been sent.
   emit RequestSent(requestId, requestConfig.numWords);

   return requestId;
}

[...]

function calculateRequestPrice() internal view returns (uint256) {
   return i_vrfV2PlusWrapper.calculateRequestPrice(requestConfig.callbackGasLimit, requestConfig.numWords);
}

After the VRF service fulfills your randomness request, it calls back your fulfillRandomWords function, where you implement logic to handle the random values. It's recommended to keep the processing in your callback function minimal to save on gas costs. In this case, the example contract updates the RequestStatus in the request mapping, and emits an event indicating that the request has been fulfilled. Another part of your application can listen for this event and further process the random values separately from the callback function.

function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override {
   require(s_requests[_requestId].paid > 0, "request not found");
   s_requests[_requestId].fulfilled = true;
   s_requests[_requestId].randomWords = _randomWords;
   emit RequestFulfilled(_requestId, _randomWords, s_requests[_requestId].paid);
}

Get the latest Chainlink content straight to your inbox.