Getting Started
Scaling enables offchain access to anchor feeds by leveraging Flare's network of 100 independent data providers and a robust commit-reveal process every 90 seconds.
To read anchor feeds on Flare, follow these key steps:
-
Fetch anchor feed data offchain:
Use the Data Availability (DA) Layer API to retrieve anchor feeds and their associated cryptographic proofs.
-
Verify the proof onchain:
Validate the provided proof onchain to ensure the data matches the finalized version committed by Scaling.
-
Use the feed data onchain:
After verification, integrate the feed data into your onchain application logic.
Scaling only stores commitments to feed data onchain. Complete feed data resides offchain but can be verified against an onchain Merkle root, ensuring data integrity and tamper resistance.
DA Layer API URLs
The public DA Layer endpoints are rate-limited. To request an API key for higher limits, create an API Key Request Issue.
The DA Layer provides API endpoints for querying offchain data from Flare protocols.
Network | Base URL |
---|---|
Flare Mainnet | https://flr-data-availability.flare.network/ |
Songbird Canary-Network | https://sgb-data-availability.flare.network/ |
All networks have the same API structure. For a full list of endpoints see Data Availability API Reference.
Fetching anchor feed data
The DA Layer API allows querying values and proofs for multiple feeds from the same voting epoch in a single request. Use the anchor-feeds-with-proof
POST endpoint to retrieve pricing data.
To fetch the feed values for FLR/USD, BTC/USD, and ETH/USD at the latest voting round, use the following command:
- curl
- Javascript
- Python
- Go
- Rust
curl -X 'POST' \
'https://flr-data-availability.flare.network/api/v0/ftso/anchor-feeds-with-proof' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"feed_ids": [
"0x01464c522f55534400000000000000000000000000",
"0x014254432f55534400000000000000000000000000",
"0x014554482f55534400000000000000000000000000"
]
}'
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
const BASE_URL = "https://flr-data-availability.flare.network/";
const API_KEY = "<your-api-key>";
// Feed IDs, see https://dev.flare.network/ftso/scaling/anchor-feeds for full list
const FEED_IDS = [
"0x01464c522f55534400000000000000000000000000", // FLR/USD
"0x014254432f55534400000000000000000000000000", // BTC/USD
"0x014554482f55534400000000000000000000000000", // ETH/USD
];
async function fetchAnchorFeeds(feedIds, votingRoundId = null) {
const url = votingRoundId
? `${BASE_URL}api/v0/ftso/anchor-feeds-with-proof?voting_round_id=${votingRoundId}`
: `${BASE_URL}api/v0/ftso/anchor-feeds-with-proof`;
return await (
await fetch(url, {
method: "POST",
headers: {
"X-API-KEY": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
feed_ids: feedIds,
}),
})
).json();
}
fetchAnchorFeeds(FEED_IDS).then((data) => {
console.log("Anchor feeds data:", data);
});
# THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
import asyncio
import aiohttp
BASE_URL = "https://flr-data-availability.flare.network/"
API_KEY = "<your-api-key>"
FEED_IDS = [
"0x01464c522f55534400000000000000000000000000", # FLR/USD
"0x014254432f55534400000000000000000000000000", # BTC/USD
"0x014554482f55534400000000000000000000000000", # ETH/USD
]
async def fetch_anchor_feeds(
feed_ids: list[str], voting_round_id: int | None = None
) -> list[dict]:
url = f"{BASE_URL}api/v0/ftso/anchor-feeds-with-proof"
if voting_round_id:
url += f"?voting_round_id={voting_round_id}"
async with (
aiohttp.ClientSession() as session,
session.post(
url,
headers={
"X-API-KEY": API_KEY,
"Content-Type": "application/json",
},
json={"feed_ids": feed_ids},
) as response,
):
return await response.json()
async def main() -> None:
data = await fetch_anchor_feeds(FEED_IDS)
print("Anchor feeds data:", data)
asyncio.run(main())
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
package flare
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
BaseURL = "https://flr-data-availability.flare.network/"
ApiKey = "<your-api-key>"
)
var FeedIds = []string{
"0x01464c522f55534400000000000000000000000000", // FLR/USD
"0x014254432f55534400000000000000000000000000", // BTC/USD
"0x014554482f55534400000000000000000000000000", // ETH/USD
}
var VotingRoundId = ""
type AnchorFeed struct {
Body struct {
VotingRoundID int `json:"votingRoundId"`
ID string `json:"id"`
Value int `json:"value"`
TurnoutBIPS int `json:"turnoutBIPS"`
Decimals int `json:"decimals"`
} `json:"body"`
Proof []string `json:"proof"`
}
func FetchAnchorFeeds() ([]AnchorFeed, error) {
url := BaseURL + "api/v0/ftso/anchor-feeds-with-proof"
if VotingRoundId != "" {
url += "?voting_round_id=" + VotingRoundId
}
payload, _ := json.Marshal(map[string]interface{}{"feed_ids": FeedIds})
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
req.Header.Set("X-API-KEY", ApiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("request failed: %v (status: %d)", err, resp.StatusCode)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var feeds []AnchorFeed
return feeds, json.Unmarshal(body, &feeds)
}
func main() {
feeds, err := FetchAnchorFeeds()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Anchor feeds: %+v\n", feeds)
}
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
#![allow(dead_code)]
#![allow(unused)]
use reqwest::Client;
use serde_json::json;
const BASE_URL: &str = "https://flr-data-availability.flare.network/";
const API_KEY: &str = "<your-api-key>";
const FEED_IDS: &[&str] = &[
"0x01464c522f55534400000000000000000000000000", // FLR/USD
"0x014254432f55534400000000000000000000000000", // BTC/USD
"0x014554482f55534400000000000000000000000000", // ETH/USD
];
pub async fn fetch_anchor_feed(
feed_ids: &[&str],
voting_round_id: Option<u32>,
) -> Result<Vec<serde_json::Value>, reqwest::Error> {
let client = Client::new();
let mut url = format!("{BASE_URL}api/v0/ftso/anchor-feeds-with-proof");
if let Some(id) = voting_round_id {
url.push_str(&format!("?voting_round_id={}", id));
}
let response = client
.post(&url)
.header("X-API-KEY", API_KEY)
.header("Content-Type", "application/json")
.json(&json!({ "feed_ids": feed_ids }))
.send()
.await?;
let json: Vec<serde_json::Value> = response.json().await?;
Ok(json)
}
#[tokio::main]
async fn main() {
match fetch_anchor_feed(FEED_IDS, None).await {
Ok(data) => println!("Anchor feeds data: {:?}", data),
Err(err) => eprintln!("Error fetching anchor feeds: {}", err),
}
}
API response structure
The response contains JSON objects for each feed, with the following fields:
votingRoundId
: The voting round ID (each round lasts 90 seconds; see the Flare Systems Explorer).id
: The feed ID (refer to the list of anchor feeds).value
: The integer value of the feed.turnoutBIPS
: The percentage of voting weight (in basis points) that contributed to the finalized value.decimals
: The number of decimal places for the feed.proof
: The Merkle proof array for data verification.
Example Response (for BTC/USD)
[
{
"body": {
"votingRoundId": 823386,
"id": "0x014254432f55534400000000000000000000000000",
"value": 9837867,
"turnoutBIPS": 9442,
"decimals": 2
},
"proof": [
"0x79b8a56bf66ae571ed4c0e3e1317825277c43f5ca3b5a85b834fb6407de03b63",
"...additional proof hashes..."
]
}
]
The floating point value of a feed can be calculated by dividing the value
by 10^decimals
. For example, if the feed value of BTC/USD is 6900420
and the decimal is 2
, the floating point feed value is 69004.20
.
Fetching Timestamps
The ftso/anchor-feeds-with-proof
endpoint returns a votingRoundId
. Each voting round lasts for a fixed duration of 90 seconds. To determine the starting timestamp, use the fsp/status
GET endpoint:
curl -X 'GET' \
'https://flr-data-availability.flare.network/api/v0/fsp/status' \
-H 'accept: application/json'
- The timestamps returned correspond to the start of the voting round, which lasts for 90 seconds.
- Prices for a given voting round are finalized at the end of the round, calculated as
start_timestamp + 90s
.
Example Response
{
"active": {
"voting_round_id": 839641,
"start_timestamp": 1733997690
},
"latest_fdc": {
"voting_round_id": -1,
"start_timestamp": -1
},
"latest_ftso": {
"voting_round_id": 839640,
"start_timestamp": 1733997600
}
}
The response indicates that:
- The currently active voting round has
voting_round_id
839641, which started at1733997690
. - The most recently finalized FTSO voting round has
voting_round_id
839640, which started at1733997600
.
Verifying proof onchain
To verify feed data onchain, use the FtsoV2Interface
. This interface offers the verifyFeedData
method to validate feed data and proof against the onchain Merkle root.
The function requires a single input struct FeedDataWithProof
, which includes the feed data and voting round ID within the FeedData
struct, and a Merkle proof.
An example contract verifying and consuming anchor feeds onchain
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import {FtsoV2Interface} from "@flarenetwork/flare-periphery-contracts/coston2/FtsoV2Interface.sol";
import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
import {TestFtsoV2Interface} from "@flarenetwork/flare-periphery-contracts/coston2/TestFtsoV2Interface.sol";
/**
* THIS IS AN EXAMPLE CONTRACT.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract FtsoV2AnchorFeedConsumer {
TestFtsoV2Interface internal ftsoV2;
mapping(uint32 => mapping(bytes21 => TestFtsoV2Interface.FeedData))
public provenFeeds;
function savePrice(
TestFtsoV2Interface.FeedDataWithProof calldata data
) public {
/* THIS IS A TEST METHOD, in production use: ftsoV2 = ContractRegistry.getFtsoV2(); */
ftsoV2 = ContractRegistry.getTestFtsoV2();
// Step 1: Verify the proof
require(ftsoV2.verifyFeedData(data), "Invalid proof");
// Step 2: Use the feed data with app specific logic
// Here the feeds are saved
provenFeeds[data.body.votingRoundId][data.body.id] = data.body;
}
}
Fetching and verifying feeds
The following example shows how to query feed and proof data from DA Layer and submit it to the onchain consumer:
- Javascript
- Python
- Go
- Rust
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
import { artifacts } from "hardhat";
import { fetchAnchorFeeds } from "./fetch_anchor_feeds";
const FtsoV2AnchorFeedConsumer = artifacts.require(
"FtsoV2AnchorFeedConsumer.sol",
);
// Feed IDs, see https://dev.flare.network/ftso/scaling/anchor-feeds for full list
const BTC_USD_FEED_ID = "0x014254432f55534400000000000000000000000000";
const TARGET_VOTING_ROUND = 823402;
async function main() {
// Deploy FtsoV2AnchorFeedConsumer contract
const feedConsumer = await FtsoV2AnchorFeedConsumer.new();
// Fetch price from DA Layer
const feedData = await fetchAnchorFeeds(
[BTC_USD_FEED_ID],
TARGET_VOTING_ROUND,
);
// Save fetched price to contract
await feedConsumer.savePrice({
proof: feedData[0].proof,
body: feedData[0].data,
});
// Query saved price from contract
const savedPrice = await feedConsumer.provenFeeds.call(
TARGET_VOTING_ROUND,
BTC_USD_FEED_ID,
);
const formattedPrice = savedPrice.value * Math.pow(10, -savedPrice.decimals);
console.log(
`Saved price: ${formattedPrice} USD at voting round: ${savedPrice.votingRoundId.toString()}`,
);
}
main();
# THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
import asyncio
from web3 import AsyncHTTPProvider, AsyncWeb3
from fetch_anchor_feeds import fetch_anchor_feeds
RPC_URL = "https://coston2-api.flare.network/ext/C/rpc"
contract_abi = [
{
"inputs": [
{"internalType": "uint32", "name": "", "type": "uint32"},
{"internalType": "bytes21", "name": "", "type": "bytes21"},
],
"name": "provenFeeds",
"outputs": [
{"internalType": "uint32", "name": "votingRoundId", "type": "uint32"},
{"internalType": "bytes21", "name": "id", "type": "bytes21"},
{"internalType": "int32", "name": "value", "type": "int32"},
{"internalType": "uint16", "name": "turnoutBIPS", "type": "uint16"},
{"internalType": "int8", "name": "decimals", "type": "int8"},
],
"stateMutability": "view",
"type": "function",
},
{
"inputs": [
{
"components": [
{"internalType": "bytes32[]", "name": "proof", "type": "bytes32[]"},
{
"components": [
{
"internalType": "uint32",
"name": "votingRoundId",
"type": "uint32",
},
{
"internalType": "bytes21",
"name": "id",
"type": "bytes21",
},
{"internalType": "int32", "name": "value", "type": "int32"},
{
"internalType": "uint16",
"name": "turnoutBIPS",
"type": "uint16",
},
{
"internalType": "int8",
"name": "decimals",
"type": "int8",
},
],
"internalType": "struct FtsoV2Interface.FeedData",
"name": "body",
"type": "tuple",
},
],
"internalType": "struct FtsoV2Interface.FeedDataWithProof",
"name": "data",
"type": "tuple",
}
],
"name": "savePrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
},
]
contract_address = "0x069227C6A947d852c55655e41C6a382868627920"
w3 = AsyncWeb3(
AsyncHTTPProvider(RPC_URL),
)
# Create contract instance
contract = w3.eth.contract(address=contract_address, abi=contract_abi)
# Feed IDs and target voting round
BTC_USD_FEED_ID = "0x014254432f55534400000000000000000000000000"
TARGET_VOTING_ROUND = 823402
async def main() -> None:
try:
feed_data = await fetch_anchor_feeds([BTC_USD_FEED_ID], TARGET_VOTING_ROUND)
proof = feed_data[0]["proof"]
body = feed_data[0]["body"]
# Create transaction
txn_hash = await contract.functions.savePrice(
{"proof": proof, "body": body}
).transact()
tx_receipt = await w3.eth.wait_for_transaction_receipt(txn_hash)
print(f"Transaction sent with hash: {tx_receipt}")
saved_price = await contract.functions.provenFeeds(
TARGET_VOTING_ROUND, BTC_USD_FEED_ID
).call()
formatted_price = saved_price[2] * (10 ** -saved_price[4])
print(f"Saved price: {formatted_price} USD at voting round: {saved_price[0]}")
except Exception as err:
print(f"Error: {err}")
# Run the main function
if __name__ == "__main__":
asyncio.run(main())
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
package flare
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func convertToByteArray(proof []string) ([][32]byte, error) {
var result [][32]byte
for _, str := range proof {
decoded, err := hex.DecodeString(str)
if err != nil {
return nil, fmt.Errorf("failed to decode string: %v", err)
}
if len(decoded) != 32 {
return nil, fmt.Errorf("decoded string is not 32 bytes: got %d bytes", len(decoded))
}
var byteArray [32]byte
copy(byteArray[:], decoded)
result = append(result, byteArray)
}
return result, nil
}
func VerifyAnchorFeedsOnchain() {
const (
PRIVATE_KEY = "your_private_key_here"
RPC_URL = "https://coston2-api.flare.network/ext/C/rpc"
DEPLOYED_CONTRACT_ADDRESS = "0x069227C6A947d852c55655e41C6a382868627920"
)
var feed_Id [21]byte
var proof [][32]byte
feeds, err := FetchAnchorFeeds()
if err != nil {
log.Fatal(err)
}
copy(feed_Id[:], feeds[0].Body.ID)
votingRoundId := uint32(feeds[0].Body.VotingRoundID)
feedBody := FtsoV2InterfaceFeedData{
votingRoundId,
feed_Id,
int32(feeds[0].Body.Value),
uint16(feeds[0].Body.TurnoutBIPS),
int8(feeds[0].Body.Decimals),
}
proof, _ = convertToByteArray(feeds[0].Proof)
var feedWithProof = FtsoV2InterfaceFeedDataWithProof{
proof,
feedBody,
}
client, err := ethclient.Dial(RPC_URL)
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA(PRIVATE_KEY)
if err != nil {
log.Fatalf("Failed to parse private key: %v", err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("Invalid public key type")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatalf("Failed to fetch nonce: %v", err)
}
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatalf("Failed to fetch gas price: %v", err)
}
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatalf("Failed to fetch chain ID: %v", err)
}
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
if err != nil {
log.Fatalf("Failed to create transactor: %v", err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) //
auth.GasLimit = uint64(3000000)
auth.GasPrice = gasPrice
consumerAddr := common.HexToAddress(DEPLOYED_CONTRACT_ADDRESS)
anchorFeed, err := NewFtsoV2AnchorFeedConsumer(consumerAddr, client)
if err != nil {
log.Fatalf("Failed to initialize consumer contract: %v", err)
}
if err != nil {
log.Fatal(err)
}
tx, err := anchorFeed.SavePrice(auth, feedWithProof)
if err != nil {
log.Fatalf("Failed to save price: %v", err)
}
fmt.Printf("SavePrice transaction hash: %s\n", tx.Hash().Hex())
savedPrice, _ := anchorFeed.ProvenFeeds(&bind.CallOpts{}, votingRoundId, feed_Id)
fmt.Printf("%+v\n", savedPrice)
}
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
use crate::FtsoV2AnchorFeedConsumer::{FeedData, FeedDataWithProof};
use alloy::{
network::EthereumWallet,
primitives::{address, hex, FixedBytes},
providers::{Provider, ProviderBuilder},
signers::local::PrivateKeySigner,
sol,
};
use hex::FromHex;
use serde_json::Value;
// Import the fetch_anchor_feeds module
mod fetch_anchor_feeds;
// ABI bindings for the Solidity contract
sol!(
#[sol(rpc)]
FtsoV2AnchorFeedConsumer,
"FtsoV2AnchorFeedConsumer.json"
);
const BTC_USD_FEED_ID: &str = "0x014254432f55534400000000000000000000000000";
const TARGET_VOTING_ROUND: u32 = 823402;
mod convert_type {
use super::*;
// Functions for type conversion
pub fn as_u16(value: &Value) -> Result<u16, Box<dyn std::error::Error>> {
value
.as_u64()
.and_then(|v| v.try_into().ok())
.ok_or("Invalid u16 value".into())
}
pub fn as_u32(value: &Value) -> Result<u32, Box<dyn std::error::Error>> {
value
.as_u64()
.and_then(|v| v.try_into().ok())
.ok_or("Invalid u32 value".into())
}
pub fn as_i8(value: &Value) -> Result<i8, Box<dyn std::error::Error>> {
value
.as_i64()
.and_then(|v| v.try_into().ok())
.ok_or("Invalid i8 value".into())
}
pub fn as_i32(value: &Value) -> Result<i32, Box<dyn std::error::Error>> {
value
.as_i64()
.and_then(|v| v.try_into().ok())
.ok_or("Invalid i32 value".into())
}
// Function to convert JSON proofs to FixedBytes
pub fn json_proofs_to_fixed_bytes(
proofs: &[Value],
) -> Result<Vec<FixedBytes<32>>, Box<dyn std::error::Error>> {
let mut result = Vec::new();
for proof in proofs {
let proof_str = proof.as_str().ok_or("Proof value is not a string")?;
// Validate and convert hex string
let fixed_bytes = hex_to_fixed_bytes_32(proof_str)?;
result.push(fixed_bytes);
}
Ok(result)
}
// Function to convert hex string to FixedBytes<32>
pub fn hex_to_fixed_bytes_32(
hex_str: &str,
) -> Result<FixedBytes<32>, Box<dyn std::error::Error>> {
// Handle both 0x-prefixed and unprefixed hex strings
let clean_hex = if hex_str.starts_with("0x") || hex_str.starts_with("0X") {
&hex_str[2..]
} else {
hex_str
};
// Validate length (64 characters = 32 bytes)
if clean_hex.len() != 64 {
return Err(format!(
"Hex string must be 64 characters (32 bytes), got {} characters",
clean_hex.len()
)
.into());
}
// Convert hex to bytes using the hex crate
let mut bytes = [0u8; 32];
hex::decode_to_slice(clean_hex, &mut bytes)
.map_err(|e| format!("Hex decoding failed: {}", e))?;
Ok(FixedBytes::<32>::from(bytes))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let feed_data: Vec<Value> =
fetch_anchor_feeds::fetch_anchor_feed(&[BTC_USD_FEED_ID], Some(TARGET_VOTING_ROUND))
.await?;
let raw_proofs = feed_data[0]["proof"]
.as_array()
.ok_or("Missing or invalid proof array")?;
let raw_body = feed_data[0]["body"]
.as_object()
.ok_or("Missing or invalid body object")?;
let byte_proof = convert_type::json_proofs_to_fixed_bytes(raw_proofs)?;
let body_data = FeedData {
decimals: convert_type::as_i8(&raw_body["decimals"])?,
id: FixedBytes::<21>::from_slice(&hex::decode(
raw_body["id"].as_str().ok_or("Invalid id format")?,
)?),
turnoutBIPS: convert_type::as_u16(&raw_body["turnoutBIPS"])?,
value: convert_type::as_i32(&raw_body["value"])?,
votingRoundId: convert_type::as_u32(&raw_body["votingRoundId"])?,
};
let contract_address = address!("069227C6A947d852c55655e41C6a382868627920");
let private_key = " your_private_key_here";
let signer: PrivateKeySigner = private_key.parse()?;
let wallet = EthereumWallet::from(signer.clone());
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_http("https://coston2-api.flare.network/ext/C/rpc".parse()?);
let latest_block = provider.get_block_number().await?;
println!("Latest block number: {latest_block}");
let contract = FtsoV2AnchorFeedConsumer::new(contract_address, provider);
let feed_data_with_proof = FeedDataWithProof {
proof: byte_proof,
body: body_data,
};
let tx_hash = contract
.savePrice(feed_data_with_proof)
.send()
.await?
.watch()
.await?;
println!("Save Price transaction hash: {}", tx_hash);
let feed_id: FixedBytes<21> = {
let hex_str = BTC_USD_FEED_ID
.strip_prefix("0x")
.unwrap_or(BTC_USD_FEED_ID);
let bytes: [u8; 21] = <[u8; 21]>::from_hex(hex_str)?;
FixedBytes::new(bytes)
};
let saved_price = contract
.provenFeeds(TARGET_VOTING_ROUND, feed_id)
.call()
.await?;
println!("Saved Price is {}", saved_price.value);
Ok(())
}