Skip to content

Getting FTSO Data Feeds#

This tutorial shows the simplest way to use the FTSO system to retrieve a specific data feed, like the price of Bitcoin.

The tutorial shows:

  • How to use the Flare periphery packages to simplify working with the Flare API.
  • How to retrieve the latest price for a given asset from the FTSO system.

Code#

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

For easy navigation, numbered comments in the source code link to the tutorial sections below.

GettingDataFeeds.sol
 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
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

// 1. Import dependencies
import "@flarenetwork/flare-periphery-contracts/flare/util-contracts/userInterfaces/IFlareContractRegistry.sol";
import "@flarenetwork/flare-periphery-contracts/flare/ftso/userInterfaces/IFtsoRegistry.sol";

contract GettingDataFeeds {

    address private constant FLARE_CONTRACT_REGISTRY =
        0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019;

    function getTokenPriceWei(
        string memory _symbol
    ) public view returns(
        uint256 _price, uint256 _timestamp, uint256 _decimals)
    {
        // 2. Access the Contract Registry
        IFlareContractRegistry contractRegistry = IFlareContractRegistry(
            FLARE_CONTRACT_REGISTRY);

        // 3. Retrieve the FTSO Registry
        IFtsoRegistry ftsoRegistry = IFtsoRegistry(
            contractRegistry.getContractAddressByName('FtsoRegistry'));

        // 4. Get latest price
        (_price, _timestamp, _decimals) =
            ftsoRegistry.getCurrentPriceWithDecimals(_symbol);
    }

}

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.20.1 @nomicfoundation/hardhat-toolbox @flarenetwork/flare-periphery-contracts
    
    If you intend to test as explained in the following steps, be aware that an open issue with Hardhat prevents using versions higher than 2.20.1.
  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 GettingDataFeeds.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 TestGettingDataFeeds.js in the test folder.
    TestGettingDataFeeds.js
    const { expect } = require("chai");
    
    describe("GettingDataFeeds", async function () {
        let contract;
        beforeEach(async function () {
            contract = await ethers.deployContract("GettingDataFeeds");
        });
        it("Should return sensible values", async function () {
            const res = await contract.getTokenPriceWei("BTC");
    
            expect(res._timestamp).to.greaterThan(1695817332);
            expect(res._decimals).to.within(0, 18);
            expect(res._price).to.within(0, 1000000 * 10 ** Number(res._decimals));
        });
    });
    
  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 GettingDataFeeds.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 GettingDataFeeds.t.sol in the test folder.
    GettingDataFeeds.t.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    // Import dependencies
    import "forge-std/Test.sol";
    import "../src/GettingDataFeeds.sol";
    
    // Test Contract
    contract TestGettingDataFeeds 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 testSimplePrice() public {
            vm.selectFork(flareFork);
            GettingDataFeeds datafeeds = new GettingDataFeeds();
    
            (uint256 _price, uint256 _timestamp, uint256 _decimals) =
                datafeeds.getTokenPriceWei("BTC");
    
            assertGt(_timestamp, 1695817332,
                "Timestamp expected to be greater than a known past block");
            assertGe(_decimals, 0,
                "Number of decimals expected to be >= 0");
            assertLe(_decimals, 18,
                "Number of decimals expected to be <= 18");
            assertGt(_price, 0,
                "Price expected to be > 0");
            assertLt(_price, 1000000 * 10 ** _decimals,
                "Price expected to be < 1'000'000");
        }
    }
    
  3. Run the test with:
    forge test -vv
    
GettingDataFeeds.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_PACKAGE = "@flarenetwork/flare-periphery-contract-artifacts";
const FLARE_RPC = "https://flare-api.flare.network/ext/bc/C/rpc";

async function runGettingDataFeeds(symbol) {
    console.log(`Retrieving current price of ${symbol}...`);

    // 1. Import dependencies
    const ethers = await import("ethers");
    const flare = await import(FLARE_PACKAGE);

    // Node to submit queries to.
    const provider = new ethers.JsonRpcProvider(FLARE_RPC);

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

    // 3. Retrieve the FTSO Registry
    const ftsoRegistryAddr = await
        flareContractRegistry.getContractAddressByName("FtsoRegistry");
    const ftsoRegistry = new ethers.Contract(
        ftsoRegistryAddr,
        flare.nameToAbi("FtsoRegistry", "flare").data,
        provider);

    // 4. Get latest price
    const [price, timestamp, decimals] =
        await ftsoRegistry["getCurrentPriceWithDecimals(string)"](symbol);

    console.log(`${Number(price) / Math.pow(10, Number(decimals))} USD`);
    console.log(`Calculated at ${new Date(Number(timestamp) * 1000)}`);
}

runGettingDataFeeds("BTC");

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 GettingDataFeeds.js.
  3. Initialize project and install dependencies with:
    npm init
    npm install ethers@6.3 @flarenetwork/flare-periphery-contract-artifacts@0.1.7
    
  4. Run the program with:
    node GettingDataFeeds.js
    

Run in browser

Tutorial#

1. Import Dependencies#

The tutorial uses the following dependencies:

6
7
import "@flarenetwork/flare-periphery-contracts/flare/util-contracts/userInterfaces/IFlareContractRegistry.sol";
import "@flarenetwork/flare-periphery-contracts/flare/ftso/userInterfaces/IFtsoRegistry.sol";
8
9
    const ethers = await import("ethers");
    const flare = await import(FLARE_PACKAGE);

The Periphery Packages simplify working with the Flare smart contracts significantly. 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.

Its address 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.

20
21
        IFlareContractRegistry contractRegistry = IFlareContractRegistry(
            FLARE_CONTRACT_REGISTRY);
15
16
17
18
    const flareContractRegistry = new ethers.Contract(
        "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019",
        flare.nameToAbi("FlareContractRegistry", "flare").data,
        provider);

3. Retrieve the FTSO Registry#

Prices for all assets tracked by the FTSO system are recovered through the FtsoRegistry contract.

Use the getContractAddressByName() method from the FlareContractRegistry to retrieve the address of the FtsoRegistry.

24
25
        IFtsoRegistry ftsoRegistry = IFtsoRegistry(
            contractRegistry.getContractAddressByName('FtsoRegistry'));
21
22
23
24
25
26
    const ftsoRegistryAddr = await
        flareContractRegistry.getContractAddressByName("FtsoRegistry");
    const ftsoRegistry = new ethers.Contract(
        ftsoRegistryAddr,
        flare.nameToAbi("FtsoRegistry", "flare").data,
        provider);

This address can be retrieved in the initialization phase of your program and used afterward. There is no need to fetch it every time it must be used.

4. Get Latest Price#

Finally, the asset's price is fetched from the FtsoRegistry using getCurrentPriceWithDecimals.

28
29
        (_price, _timestamp, _decimals) =
            ftsoRegistry.getCurrentPriceWithDecimals(_symbol);
29
30
31
32
33
    const [price, timestamp, decimals] =
        await ftsoRegistry["getCurrentPriceWithDecimals(string)"](symbol);

    console.log(`${Number(price) / Math.pow(10, Number(decimals))} USD`);
    console.log(`Calculated at ${new Date(Number(timestamp) * 1000)}`);
  • The only parameter of this method is the symbol for the asset being queried, like "FLR" or "BTC". You can use getSupportedSymbols() to retrieve the list of all supported symbols.

    Warning

    On Coston and Coston2, the symbol names are prefixed with "test", such as "testBTC". When you use the getSupportedSymbols() function to retrieve the list of supported symbols, the symbol names will already contain the prefix.

  • Given that Solidity does not support numbers with decimals, this method returns the requested price as an integer and the number of decimal places by which the comma must be shifted.

    For example, if it returns 1234 for the price and 2 for the decimals, the actual price of the asset in USD is 12.34.

  • It also returns the time when the price was calculated by the FTSO system as a UNIX timestamp. You can use an online tool like EpochConverter to turn the timestamp into a human-readable form, or use Date as in the JavaScript example.

JavaScript note on overloaded methods

The call to the getCurrentPriceWithDecimals method is a bit cumbersome in JavaScript:

37
38
    const [price, timestamp, decimals] =
        await ftsoRegistry["getCurrentPriceWithDecimals(string)"](symbol);

The call needs to be like this because this method is overloaded. getCurrentPriceWithDecimals has two versions: one accepting a string for the symbol and another one accepting an integer for the asset's index in the FTSO system. Therefore, the call needs to disambiguate both versions.

The vast majority of methods are not overloaded and allow a more natural call format. For example:

await ftsoRegistry.getSupportedSymbols();

Conclusion#

This tutorial served as the Hello World program for the FTSO system. It has shown:

  • How to use the Flare Periphery Package, both from Solidity and from JavaScript.
  • How to retrieve the latest price for a given asset from the FTSO system.