Programmatic Subscription

How you manage the subscription depends on your randomness needs. You can configure your subscriptions using the Subscription Manager, but these examples demonstrate how to create your subscription and add your consumer contracts programmatically. For these examples, the contract owns and manages the subscription. Any wallet can provide funding to those subscriptions.

You can view and monitor your subscriptions in the Subscription Manager even if you create them programmatically. Go to vrf.chain.link to open the Subscription Manager.

Modifying subscriptions and configurations

Subscription configurations do not have to be static. You can change your subscription configuration dynamically by calling the following functions using the VRFCoordinatorV2Interface:

  • Change the list of approved subscription consumers with:
    • addConsumer(uint64 subId, address consumer).
    • removeConsumer(uint64 subId, address consumer).
  • Transfer the subscription ownership with:
    • requestSubscriptionOwnerTransfer(uint64 subId, address newOwner).
    • acceptSubscriptionOwnerTransfer(uint64 subId).
  • View the subscription with getSubscription(uint64 subId).
  • Cancel the subscription with cancelSubscription(uint64 subId).

To send LINK to the subscription balance, use the LINK token interface with LINKTOKEN.transferAndCall(address(COORDINATOR), amount, abi.encode(subId)). Any wallet can fund a subscription.

See the example in the Subscription manager contract section to learn how to create a contract that can change your subscription configuration.

Subscription manager contract

In this example, the contract operates as a subscription owner and can run functions to add consuming contracts to the subscription. The consuming contracts must include the requestRandomWords() function with the correct coordinator parameters and the correct subscription ID to request random values and use the subscription balance. The consuming contracts must also include the fulfillRandomWords() function to receive the random values.

Subscription owners and consumers do not have to be separate. This contract not only allows adding consumers with addConsumer(address consumerAddress) but can also act as a consumer by running its own requestRandomWords() function. This example contract includes a createNewSubscription() function in the constructor() that creates the subscription and adds itself as a consumer automatically when you deploy it.

// SPDX-License-Identifier: MIT
// An example of a consumer contract that also owns and manages the subscription
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract VRFv2SubscriptionManager is VRFConsumerBaseV2 {
    VRFCoordinatorV2Interface COORDINATOR;
    LinkTokenInterface LINKTOKEN;

    // Goerli coordinator. For other networks,
    // see https://docs.chain.link/docs/vrf-contracts/#configurations
    address vrfCoordinator = 0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D;

    // Goerli LINK token contract. For other networks, see
    // https://docs.chain.link/docs/vrf-contracts/#configurations
    address link_token_contract = 0x326C977E6efc84E512bB9C30f76E30c160eD06FB;

    // The gas lane to use, which specifies the maximum gas price to bump to.
    // For a list of available gas lanes on each network,
    // see https://docs.chain.link/docs/vrf-contracts/#configurations
    bytes32 keyHash =
        0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;

    // A reasonable default is 100000, but this value could be different
    // on other networks.
    uint32 callbackGasLimit = 100000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFCoordinatorV2.MAX_NUM_WORDS.
    uint32 numWords = 2;

    // Storage parameters
    uint256[] public s_randomWords;
    uint256 public s_requestId;
    uint64 public s_subscriptionId;
    address s_owner;

    constructor() VRFConsumerBaseV2(vrfCoordinator) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        LINKTOKEN = LinkTokenInterface(link_token_contract);
        s_owner = msg.sender;
        //Create a new subscription when you deploy the contract.
        createNewSubscription();
    }

    // Assumes the subscription is funded sufficiently.
    function requestRandomWords() external onlyOwner {
        // Will revert if subscription is not set and funded.
        s_requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
    }

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

    // Create a new subscription when the contract is initially deployed.
    function createNewSubscription() private onlyOwner {
        s_subscriptionId = COORDINATOR.createSubscription();
        // Add this contract as a consumer of its own subscription.
        COORDINATOR.addConsumer(s_subscriptionId, address(this));
    }

    // Assumes this contract owns link.
    // 1000000000000000000 = 1 LINK
    function topUpSubscription(uint256 amount) external onlyOwner {
        LINKTOKEN.transferAndCall(
            address(COORDINATOR),
            amount,
            abi.encode(s_subscriptionId)
        );
    }

    function addConsumer(address consumerAddress) external onlyOwner {
        // Add a consumer contract to the subscription.
        COORDINATOR.addConsumer(s_subscriptionId, consumerAddress);
    }

    function removeConsumer(address consumerAddress) external onlyOwner {
        // Remove a consumer contract from the subscription.
        COORDINATOR.removeConsumer(s_subscriptionId, consumerAddress);
    }

    function cancelSubscription(address receivingWallet) external onlyOwner {
        // Cancel the subscription and send the remaining LINK to a wallet address.
        COORDINATOR.cancelSubscription(s_subscriptionId, receivingWallet);
        s_subscriptionId = 0;
    }

    // Transfer this contract's funds to an address.
    // 1000000000000000000 = 1 LINK
    function withdraw(uint256 amount, address to) external onlyOwner {
        LINKTOKEN.transfer(to, amount);
    }

    modifier onlyOwner() {
        require(msg.sender == s_owner);
        _;
    }
}

To use this contract, compile and deploy it in Remix.

  1. Open the contract in Remix.

  2. Compile and deploy the contract using the Injected Provider environment. The contract includes all of the configuration variables that you need, but you can edit them if necessary. For a full list of available configuration variables, see the Supported Networks page.

    This contract automatically creates a new subscription when you deploy it. Read the s_subscriptionId variable to find your subscription ID. You can use this value to find the subscription at vrf.chain.link.

  3. In this example, the topUpSubscription() function sends LINK from your contract to the subscription. Fund your contract with at least three testnet LINK. Alternatively, you can send LINK directly to the subscription at vrf.chain.link. Any address can provide funding to a subscription balance. If you need testnet LINK, you can get it from the Chainlink faucet.

  4. Run the topUpSubscription() function to send LINK from your contract to your subscription balance. For this example, specify a value of 3000000000000000000, which is equivalent to three LINK.

  5. Run the requestRandomWords() function. The request might take several minutes to process. Track the pending request status at vrf.chain.link.

  6. You can also add and test consumer contracts using the same programmatic subscription process:

    1. Create and deploy a consumer contract that includes the following components:

      • The requestRandomWords() function and the required variables and your subscription ID.
      • The fulfillRandomWords() callback function.

      You can use the example from the Get a Random Number guide.

    2. After you deploy the consumer contract, add it to the subscription as an approved consumer using the addConsumer() function on your subscription manager contract. Specify the address of your consumer contract.

    3. On the consumer contract, run the requestRandomWords() function to request and receive random values. The request might take several minutes to process. Track the pending request status at vrf.chain.link.

      The consumer contract can continue to make requests until your subscription balance runs out. The subscription manager contract must maintain sufficient balance in the subscription so that the consumers can continue to operate.

    4. If you need to remove consumer contracts from the subscription, use the removeConsumer() function. Specify the address of the consumer contract to be removed.

  7. When you are done with your contracts and the subscription, run the cancelSubscription() function to close the subscription and send the remaining LINK to your wallet address. Specify the address of the receiving wallet.

Funding and requesting simultaneously

You can fund a subscription and request randomness in a single transaction. You must estimate how much the transaction might cost and determine the amount of funding to send to the subscription yourself. See the Subscription billing page to learn how to estimate request costs.

// Assumes this contract owns link
// You must estimate LINK cost yourself based on the gas lane and limits.
// 1_000_000_000_000_000_000 = 1 LINK
function fundAndRequestRandomWords(uint256 amount) external onlyOwner {
    LINKTOKEN.transferAndCall(
        address(COORDINATOR),
        amount,
        abi.encode(s_subscriptionId)
    );
    // Will revert if subscription is not set and funded.
    s_requestId = COORDINATOR.requestRandomWords(
        keyHash,
        s_subscriptionId,
        requestConfirmations,
        callbackGasLimit,
        numWords
    );
}

Add this function to your contracts if you need to provide funding simultaneously with your requests. The transferAndCall() function sends LINK from your contract to the subscription, and the requestRandomWords() function requests the random words. Your contract still needs the fulfillRandomWords() callback function to receive the random values.

Whats Next

Stay updated on the latest Chainlink news