FlareForge

Guide · Oracle

Read FTSOv2 prices in 10 lines of Solidity

Flare FTSOv2 is a block-latency price oracle: a new value for every crypto pair lands on-chain roughly every 1.8 seconds, no user-triggered submission, no subscription. This guide is a straight walkthrough of how to read a feed from Solidity, Python, and TypeScript, how to handle stale data, and which feed ID to use for which pair.

Solidity: the entire integration

Flare publishes a ContractRegistrylibrary that resolves the live FTSOv2 address for you. That's the whole integration:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {ContractRegistry} from
    "@flarenetwork/flare-periphery-contracts/flare/ContractRegistry.sol";
import {IFtsoV2} from
    "@flarenetwork/flare-periphery-contracts/flare/IFtsoV2.sol";

contract PriceReader {
    bytes21 constant BTC_USD = 0x014254432f55534400000000000000000000000000;
    uint64 constant MAX_STALENESS = 60; // seconds

    function btcUsd() external view returns (uint256 value, int8 decimals) {
        IFtsoV2 ftso = ContractRegistry.getFtsoV2();
        uint64 ts;
        (value, decimals, ts) = ftso.getFeedById(BTC_USD);
        require(block.timestamp - ts <= MAX_STALENESS, "FTSO: stale");
    }
}

The stale-price require is the only thing you must add. Block-latency feeds update roughly every 1.8 seconds on Flare, so 60 seconds is a generous ceiling; anything past that means the provider committee failed to publish and you should not quote against it.

Python: over plain HTTP, no wallet

If you just want the latest value for a dashboard, a bot, or a CSV pipeline, the FlareForge API exposes the indexed feed state. No contract calls, no RPC key:

import json
from urllib.request import urlopen

BASE = "https://flareforge.io/api/v1/oracle-lab"

def latest(symbol: str) -> tuple[float, str]:
    feeds = json.loads(urlopen(f"{BASE}/feeds", timeout=5).read())["feeds"]
    match = next(f for f in feeds if f["symbol"] == symbol)
    return float(match["last_value"]) / (10 ** match["last_decimals"]), match["last_update_ts"]

value, ts = latest("BTC/USD")
print(f"BTC/USD = {value:,.2f} (updated {ts})")

The API already divides by 10^decimals and returns the price as a string to preserve precision — cast it to Decimal if rounding matters.

TypeScript / viem: direct RPC

For a frontend, read the feed straight from the contract with viem. The FTSOv2 address below is the current proxy on Flare mainnet — resolve it through ContractRegistry if you want to be proof against future redeployments.

import { createPublicClient, http, parseAbi } from "viem";
import { flare } from "viem/chains";

const FTSO_V2 = "0x..." as const; // ContractRegistry.getFtsoV2() on Flare
const abi = parseAbi([
  "function getFeedById(bytes21 feedId) view returns (uint256 value, int8 decimals, uint64 timestamp)",
]);

const client = createPublicClient({ chain: flare, transport: http() });

export async function readBtcUsd(): Promise<number> {
  const [value, decimals, ts] = await client.readContract({
    address: FTSO_V2,
    abi,
    functionName: "getFeedById",
    args: ["0x014254432f55534400000000000000000000000000"],
  });
  if (Date.now() / 1000 - Number(ts) > 60) throw new Error("FTSO: stale");
  return Number(value) / 10 ** decimals;
}

Feed ID cheat sheet

Feed IDs are bytes21: first byte is the category (0x01 = crypto, 0x02 = forex, 0x03 = commodity), next 20 bytes are the UTF-8 symbol padded right with zero bytes. A few common ones:

SymbolCategorybytes21 feed ID
FLR/USDCrypto0x01464c522f55534400000000000000000000000000
BTC/USDCrypto0x014254432f55534400000000000000000000000000
ETH/USDCrypto0x014554482f55534400000000000000000000000000
XRP/USDCrypto0x015852502f55534400000000000000000000000000
ADA/USDCrypto0x014144412f55534400000000000000000000000000
ALGO/USDCrypto0x01414c474f2f555344000000000000000000000000
DOGE/USDCrypto0x01444f47452f555344000000000000000000000000
LTC/USDCrypto0x014c54432f55534400000000000000000000000000

Full live list, sortable by category and with current price, is on Oracle Lab. 30+ crypto pairs, plus forex and commodity categories that go live as Flare rolls them out.

How to encode your own feed ID

If a pair isn't in the cheat sheet, build the ID in three lines of Python:

def encode_feed_id(category: int, symbol: str) -> str:
    body = symbol.encode("ascii")
    padded = body + b"\x00" * (20 - len(body))
    return "0x" + bytes([category]).hex() + padded.hex()

print(encode_feed_id(0x01, "SOL/USD"))
# 0x01534f4c2f55534400000000000000000000000000

When block-latency is not enough: FTSO Scaling

Block-latency feeds are pushed by the anchor voters and are trivially readable in Solidity. If you need higher accuracy or a feed that isn't on the anchor set, Flare also exposes FTSO Scaling: 1000+ feeds published as a Merkle root on-chain every 90 seconds, with the individual values served off-chain. The off-chain value comes with a Merkle proof that you verify inside your contract before you act on the number.

For most dapps, block-latency is fine. Switch to Scaling only when you need a feed the anchor set doesn't provide, or when you're building a protocol that settles against a more precise value than the block-latency median.

Related on the site