Skip to content

Getting Random Numbers#

This tutorial shows how to obtain random numbers from the Flare Systems Protocol (FSP), the infrastructure that powers most current Flare protocols. The source of the randomness is the submissions from all FTSO data providers and is therefore not centralized.

Random numbers are generated every 90 seconds and can be read directly from a smart contract.

This is useful in several development contexts where secure, fair random numbers are required, such as in games and certain blockchain protocol functionalities such as selecting a random vote power block.

Security and Fairness

A generated random number is tagged as secure if all data providers correctly followed the FTSO protocol and at least one of them is not malicious.

If a number is tagged as secure, then the protocol guarantees its fairness, meaning that it has no bias and all outcomes are equally probable.

This tutorial shows:

  • How to obtain a random number.
  • How to use the Flare periphery packages to simplify working with the Flare API.

Code#

Choose your preferred programming language and ensure you have a working development environment.

For easy navigation, numbered comments in the source code (e.g. // 1.) link to the tutorial sections below.

GetRandomNumber.sol
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import {IRelay} from "@flarenetwork/flare-periphery-contracts/coston/util-contracts/userInterfaces/IRelay.sol";
import {FlareContractsRegistryLibrary} from "@flarenetwork/flare-periphery-contracts/coston/util-contracts/ContractRegistryLibrary.sol";

contract GetRandomNumber {
    function generateNumber() external view returns (uint256, bool, uint256) {
        address relayAddress =
            FlareContractsRegistryLibrary.getContractAddressByName("Relay");
        IRelay relay = IRelay(relayAddress);
        (uint256 randomNumber, bool isSecure, uint256 timestamp) =
            relay.getRandomNumber();

        return (randomNumber, isSecure, timestamp);
    }
}

Source code license

Building with Hardhat
  1. Create a new folder and move into it.
  2. Initialize a new npm project and install dependencies:
    npm init
    npm install hardhat@2.22.3 @nomicfoundation/hardhat-toolbox @flarenetwork/flare-periphery-contracts
    
    We recommend using this version of Hardhat for testing as it is known to work well.
  3. Create a new Hardhat project (More information in the Hardhat setup guide):
    npx hardhat init
    
  4. You will not be using the sample project, therefore:
    • Remove contracts/Lock.sol
    • Remove test/Lock.js
  5. Edit hardhat.config.js to specify the correct EVM version. Make sure you include the highlighted lines:
    hardhat.config.js
    require("@nomicfoundation/hardhat-toolbox");
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
        solidity: {
            compilers: [{
                version: "0.8.17",
                settings: {
                    evmVersion: "london"
                },
            }],
        }
    };
    
  6. Copy the Solidity code above into a new file called GetRandomNumber.sol in the contracts folder.
  7. Compile with:
    npx hardhat compile
    
Testing with Hardhat

Testing smart contracts before deploying them is typically performed by forking the network or by using mock contracts. These instructions quickly show you how to use the former.

  1. Build the Hardhat project following the previous instructions.
  2. Include network information in the hardhat.config.js file. Make sure you include the highlighted lines:
    hardhat.config.js
    require("@nomicfoundation/hardhat-toolbox");
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
        solidity: {
            compilers: [{
                version: "0.8.17",
                settings: {
                    evmVersion: "london"
                },
            }],
        },
        networks: {
            hardhat: {
                forking: {
                    url: 'https://flare-api.flare.network/ext/bc/C/rpc',
                },
            },
        },
    };
    
  3. Copy the code below into a new file called TestGetRandomNumber.js in the test folder.
    TestGetRandomNumber.js
    const { expect } = require("chai");
    describe("Test Random Number", function () {
      let contract;
      beforeEach(async function () {
        contract = await ethers.deployContract("GetRandomNumber");
      });
    
      it("RandomNumber", async function () {
        const [randomNumber, isSecure, timestamp] = await contract.generateNumber();
        expect(randomNumber).to.be.at.least(
          1000000000000000000000000000000000000000n
        );
        expect(isSecure).to.be.true;
        expect(timestamp).to.be.gt(1695817332);
      });
    });
    
  4. Run the test with:
    npx hardhat test
    
Building with Foundry
  1. If you don't have Foundry installed, follow the instructions for your operating system in the Foundry's Installation guide.
  2. Create a new Foundry project:
    forge init <PROJECT_NAME>
    
    This command creates a new directory called <PROJECT_NAME>. Use a name that suits your needs.
  3. Move into the project's directory:
    cd <PROJECT_NAME>
    
  4. Install dependencies with:
    forge install flare-foundation/flare-foundry-periphery-package
    
  5. Remove the sample project that Foundry created for you, as you do not need it:
    • Remove src/Counter.sol
    • Remove test/Counter.t.sol
  6. Copy the Solidity code above into a new file called GetRandomNumber.sol in the src folder.
  7. Open the foundry.toml file, and add the following lines at the end:
    evm_version = "london"
    remappings = [ "@flarenetwork/flare-periphery-contracts/=lib/flare-foundry-periphery-package/src/"]
    
  8. Compile with:
    forge build
    
Testing with Foundry

Testing smart contracts before deploying them is typically performed by forking the network or by using mock contracts. These instructions quickly show you how to use the former.

  1. Build the Foundry project following the previous instructions.
  2. Copy the code below into a new file called GetRandomNumber.t.sol in the test folder.
    GetRandomNumber.t.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    // Import dependencies
    import "forge-std/Test.sol";
    import "../src/GetRandomNumber.sol";
    
    // Test Contract
    contract GetRandomNumberTest is Test {
        string private constant FLARE_RPC =
            "https://flare-api.flare.network/ext/bc/C/rpc";
        uint256 private flareFork;
    
        function setUp() public {
            flareFork = vm.createFork(FLARE_RPC);
        }
    
        function testRandomNumber() public {
            vm.selectFork(flareFork);
            GetRandomNumber randNumber = new GetRandomNumber();
    
            (uint256 _randomNumber, bool _isSecure, uint256 _timeStamp) = randNumber
                .getRandomNumber();
    
            assertGt(_randomNumber, 0, "Random Number expected to be > 0");
            assertTrue(_isSecure, "Expect to be true");
            assertGt(
                _timestamp,
                1695817332,
                "Timestamp expected to be greater than a known past block"
            );
        }
    }
    
  3. Run the test with:
    forge test -vv
    
GetRandomNumber.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const FLARE_CONTRACTS = "@flarenetwork/flare-periphery-contract-artifacts";
const FLARE_RPC = "https://coston-api.flare.network/ext/C/rpc";
const FLARE_CONTRACT_REGISTRY_ADDR =
  "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019";

async function runGetRandomNumber() {
  // 1. Import Dependencies
  const ethers = await import("ethers");
  const flare = await import(FLARE_CONTRACTS);
  const provider = new ethers.JsonRpcProvider(FLARE_RPC);

  // 2. Access the Contract Registry
  const flareContractRegistry = new ethers.Contract(
    FLARE_CONTRACT_REGISTRY_ADDR,
    flare.nameToAbi("FlareContractRegistry", "coston").data,
    provider
  );

  // 3. Retrieve the Relay Contract
  const relayAddress = await flareContractRegistry.getContractAddressByName(
    "Relay"
  );
  const relay = new ethers.Contract(
    relayAddress,
    flare.nameToAbi("IRelay", "coston").data,
    provider
  );

  // 4. Get the Random Number
  const [randomNumber, isSecure, timestamp] = await relay.getRandomNumber();
  console.log("Random Number is", randomNumber);
  console.log("Is it secure", isSecure);
  console.log("Creation timestamp is", timestamp);
}

runGetRandomNumber();

Source code license.

Run with Node.js

This tutorial has been tested with npm v9.5 and Node.js v18.16.

  1. Create a new folder and move into it.
  2. Copy & paste the code above into a new file called GetRandomNumber.js.
  3. Initialize project and install dependencies with:
    npm init
    npm install ethers@6.3 @flarenetwork/flare-periphery-contract-artifacts@0.1.15
    
  4. Run the program with:
    node GetRandomNumber.js
    

Tutorial#

1. Import Dependencies#

The tutorial uses the following dependencies:

3
4
import {IRelay} from "@flarenetwork/flare-periphery-contracts/coston/util-contracts/userInterfaces/IRelay.sol";
import {FlareContractsRegistryLibrary} from "@flarenetwork/flare-periphery-contracts/coston/util-contracts/ContractRegistryLibrary.sol";
 8
 9
10
  const ethers = await import("ethers");
  const flare = await import(FLARE_CONTRACTS);
  const provider = new ethers.JsonRpcProvider(FLARE_RPC);

The Periphery Packages simplify working with the Flare smart contracts significantly.

Warning

If you remove this dependency, you must manually provide the signatures for all the methods you want to use.

2. Access the Contract Registry#

The FlareContractRegistry contains the current addresses for all Flare smart contracts, and it is the only recommended way to retrieve them.

The FlareContractsRegistryLibrary contract from the Flare Periphery Package accesses the Flare Contract Registry for you, as shown next.

The address of the Flare Contract Registry is the same on all of Flare's networks, and it is the only Flare address that needs to be hard-coded into any program.

GetRandomNumber.js
3
4
const FLARE_CONTRACT_REGISTRY_ADDR =
  "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019";
GetRandomNumber.js
13
14
15
16
17
  const flareContractRegistry = new ethers.Contract(
    FLARE_CONTRACT_REGISTRY_ADDR,
    flare.nameToAbi("FlareContractRegistry", "coston").data,
    provider
  );

3. Retrieve the Relay Contract#

Use the getContractAddressByName() method of the FlareContractRegistry smart contract to retrieve the address of the Relay smart contract.

 9
10
11
        address relayAddress =
            FlareContractsRegistryLibrary.getContractAddressByName("Relay");
        IRelay relay = IRelay(relayAddress);
20
21
22
23
24
25
26
27
  const relayAddress = await flareContractRegistry.getContractAddressByName(
    "Relay"
  );
  const relay = new ethers.Contract(
    relayAddress,
    flare.nameToAbi("IRelay", "coston").data,
    provider
  );

4. Get the Random Number#

Get the latest generated random number by calling the getRandomNumber() method of the Relay contract.

12
13
14
15
        (uint256 randomNumber, bool isSecure, uint256 timestamp) =
            relay.getRandomNumber();

        return (randomNumber, isSecure, timestamp);
30
31
32
33
  const [randomNumber, isSecure, timestamp] = await relay.getRandomNumber();
  console.log("Random Number is", randomNumber);
  console.log("Is it secure", isSecure);
  console.log("Creation timestamp is", timestamp);

In addition to the randomNumber, two other variables are retrieved:

  • isSecure is a boolean flag that indicates whether the random number was generated securely, according to the description given in the introduction.

    The random number is based on all the data providers' submissions and is therefore decentralized, improving transparency and fairness. However, this decentralization makes the protocol slightly open to attempts at manipulation. If such manipulation attempts are detected, the isSecure flag is set to false, and dapps can decide whether they should discard the generated number.

  • timestamp is the UNIX timestamp indicating the time at the end of the voting epoch during which data was collected from data providers to generate this particular number.

    The timestamp can be useful, for example, to ensure that certain actions have been performed before the random number was generated. For example, in a roulette game, to ensure all bets were placed before the number was generated. Each voting epoch is a fixed 90-second window.

Conclusion#

This tutorial has shown:

  • How to use the Flare Periphery Package, both from Solidity and from JavaScript, to work with the Flare API.
  • How to get the latest random number via the Relay contract.