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:
| Symbol | Category | bytes21 feed ID |
|---|---|---|
| FLR/USD | Crypto | 0x01464c522f55534400000000000000000000000000 |
| BTC/USD | Crypto | 0x014254432f55534400000000000000000000000000 |
| ETH/USD | Crypto | 0x014554482f55534400000000000000000000000000 |
| XRP/USD | Crypto | 0x015852502f55534400000000000000000000000000 |
| ADA/USD | Crypto | 0x014144412f55534400000000000000000000000000 |
| ALGO/USD | Crypto | 0x01414c474f2f555344000000000000000000000000 |
| DOGE/USD | Crypto | 0x01444f47452f555344000000000000000000000000 |
| LTC/USD | Crypto | 0x014c54432f55534400000000000000000000000000 |
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"))
# 0x01534f4c2f55534400000000000000000000000000When 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
- 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.