netFLARE / 14
block—
blktime—
ftso—
fdc/24h—
tvl—
indexer—
FlareForgeconsole
AgentsFDCFlowSandboxDocsEmbedEcosystemAbout
mainnet live
docs / ftso-prices

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

Block-latency feeds are pushed by the anchor voters every voting round, about 90 seconds apart. That is fine for most dapps. Flare exposes two higher-resolution paths when it isn't.

FTSO Fast Updates commits incremental price deltas every block for a subset of anchor feeds, giving you sub-second effective latency on the pairs covered. Reads cost a fetch fee in native FLR for on-chain transactions; off-chain eth_call reads are free. Reach for it in liquidation engines and perpetuals funding. Walkthrough: Read FTSO Fast Updates in Solidity.

FTSO Scalingpublishes 1000+ feeds 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. Use it when you need a feed the anchor set doesn't provide. Walkthrough: Read FTSO Scaling feeds with Merkle proofs.

Related on the site

  • Oracle Lab, live catalog of every active feed, with price history and a backtester for FTSO data-provider strategies.
  • GET /api/v1/oracle-lab/feeds, every feed + its current value.
  • GET /api/v1/oracle-lab/feeds/{feed_id}/history?hours=24&bucket=5m, time-series for a single feed.
  • FAssets risk guide, same API style, for the FAssets agent risk endpoints.

Next guide →

Read FTSO Fast Updates in Solidity: sub-block oracle prices on Flare