|
| 1 | +--- |
| 2 | +title: Create a Custom Feed |
| 3 | +tags: [intermediate, ftso, solidity] |
| 4 | +slug: create-custom-feed |
| 5 | +description: Bring any time-series data onchain with Custom Feeds. |
| 6 | +keywords: |
| 7 | + [ |
| 8 | + ftso, |
| 9 | + oracle, |
| 10 | + custom feeds, |
| 11 | + data feeds, |
| 12 | + flare-time-series-oracle, |
| 13 | + flare-network, |
| 14 | + smart-contracts, |
| 15 | + solidity, |
| 16 | + ] |
| 17 | +sidebar_position: 6 |
| 18 | +--- |
| 19 | + |
| 20 | +import CodeBlock from "@theme/CodeBlock"; |
| 21 | +import PriceVerifierCustomFeedSol from "!!raw-loader!/examples/developer-hub-solidity/PriceVerifierCustomFeed.sol"; |
| 22 | +import PriceVerificationTs from "!!raw-loader!/examples/developer-hub-javascript/PriceVerification.ts"; |
| 23 | +import NewGithubIssue from "@site/src/components/newGithubIssue"; |
| 24 | + |
| 25 | +Custom Feeds, introduced in [FIP.13](https://proposals.flare.network/FIP/FIP_13.html), extend the FTSO [block-latency feeds](/ftso/feeds) by enabling developers to create onchain feeds for arbitrary time-series data. |
| 26 | +Unlike standard FTSO feeds, which are secured by a decentralized network of data providers, Custom Feeds derive their values from logic defined in a developer-controlled smart contract. |
| 27 | +This expands the FTSO's capabilities beyond traditional price pairs, allowing for a wider variety of data to be brought onto the Flare network, such as prices for Liquid Staked Tokens (LSTs), data from specific offchain APIs, or other bespoke datasets. |
| 28 | + |
| 29 | +:::warning[Risk Profile] |
| 30 | + |
| 31 | +Each Custom Feed has a unique risk profile determined by its smart contract and data source, which users and developers must assess individually. |
| 32 | + |
| 33 | +::: |
| 34 | + |
| 35 | +This guide demonstrates how to build a Custom Feed feed that uses the [Flare Data Connector (FDC)](/fdc/overview) to fetch data from an external API, verify it onchain, and make it available to other smart contracts. |
| 36 | + |
| 37 | +## Prerequisites |
| 38 | + |
| 39 | +Before you begin, ensure you have: |
| 40 | + |
| 41 | +- A conceptual understanding of smart contracts and Solidity. |
| 42 | +- Familiarity with TypeScript/JavaScript and Node.js. |
| 43 | +- Hardhat development environment set up. |
| 44 | +- Knowledge of the FTSO and an overview of the Flare Data Connector (FDC). |
| 45 | +- Environment variables set up for `WEB2JSON_VERIFIER_URL_TESTNET`, `VERIFIER_API_KEY_TESTNET`, and `COSTON2_DA_LAYER_URL` as used in the `PriceVerification.ts` script. |
| 46 | + |
| 47 | +## Concepts |
| 48 | + |
| 49 | +### `IICustomFeed` Interface |
| 50 | + |
| 51 | +To ensure compatibility with the FTSO system, any custom feed contract must implement the `IICustomFeed` interface. |
| 52 | +This interface, found in the [`@flarenetwork/flare-periphery-contracts`](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package, acts as a standard entry point for consumers of the data. |
| 53 | + |
| 54 | +#### Key functions |
| 55 | + |
| 56 | +- `feedId() external view returns (bytes21 _feedId)`:Returns the feed's unique identifier. The first byte must be 0x21 to signify a custom feed. |
| 57 | +- `read() public view returns (uint256 value)`: Returns the latest value of the feed. |
| 58 | +- `decimals() external view returns (int8)`: Returns the number of decimals for the feed's value. |
| 59 | +- `calculateFee() external pure returns (uint256 _fee)`: Calculates the fee for reading the feed. This can be zero. |
| 60 | +- `getCurrentFeed() external payable returns (uint256 _value, int8 _decimals, uint64 _timestamp)`: The primary method for retrieving the current feed data, including value, decimals, and a timestamp. |
| 61 | + |
| 62 | +### Onchain contract: `PriceVerifierCustomFeed.sol` |
| 63 | + |
| 64 | +The `PriceVerifierCustomFeed.sol` contract is designed to store a historical price for a crypto asset and allow this price to be updated by verifying a proof from the [Web2Json](/fdc/attestation-types/web-2-json) FDC attestation type. |
| 65 | +It then exposes this price through the `IICustomFeed` interface. |
| 66 | + |
| 67 | +#### Key components |
| 68 | + |
| 69 | +- **State Variables**: The contract stores its configuration and the latest verified price. |
| 70 | + |
| 71 | + ```solidity |
| 72 | + // --- State Variables --- |
| 73 | + bytes21 public immutable feedIdentifier; // Unique ID for this custom feed. ID, e.g., 0x21 + keccak256("BTC/USD-HIST") |
| 74 | + string public expectedSymbol; // Asset symbol this feed tracks (e.g., "BTC"). |
| 75 | + int8 public decimals_; // Precision for the price. |
| 76 | + uint256 public latestVerifiedPrice; // Stores the most recent verified price. |
| 77 | + address public owner; // Address that deployed the contract, with privileges to update mappings. |
| 78 | + ``` |
| 79 | + |
| 80 | +- **Constructor**: Initializes the feed's immutable properties, such as its `feedIdentifier`, symbol, and decimals. It also sets up an initial mapping of asset symbols (e.g., "BTC") to API-specific identifiers (e.g., CoinGecko's "bitcoin"). |
| 81 | + |
| 82 | + ```solidity |
| 83 | + constructor( |
| 84 | + bytes21 _feedId, |
| 85 | + string memory _expectedSymbol, |
| 86 | + int8 _decimals |
| 87 | + ) { |
| 88 | + // ... validation logic ... |
| 89 | + owner = msg.sender; |
| 90 | + feedIdentifier = _feedId; |
| 91 | + expectedSymbol = _expectedSymbol; |
| 92 | + decimals_ = _decimals; |
| 93 | + // ... |
| 94 | + } |
| 95 | + ``` |
| 96 | + |
| 97 | +- **`verifyPrice(IWeb2Json.Proof calldata _proof)`**: |
| 98 | + This is the heart of the contract. |
| 99 | + It accepts a proof from the FDC and performs a series of checks before updating the `latestVerifiedPrice`. |
| 100 | + |
| 101 | + 1. **Parses the API URL** from the proof to ensure the data is from the expected source. |
| 102 | + |
| 103 | + 2. **Verifies the proof's authenticity** by calling the FDC's onchain `verifyJsonApi` function. |
| 104 | + |
| 105 | + 3. **Decodes the price data** from the proof's response body. |
| 106 | + |
| 107 | + 4. **Stores the new price** in the `latestVerifiedPrice` state variable. |
| 108 | + |
| 109 | + 5. **Emits an event** to log the update. |
| 110 | + |
| 111 | + ```solidity |
| 112 | + function verifyPrice(IWeb2Json.Proof calldata _proof) external { |
| 113 | + require(ContractRegistry.getFdcVerification().verifyJsonApi(_proof), "FDC: Invalid Web2Json proof"); |
| 114 | +
|
| 115 | + PriceData memory newPriceData = abi.decode(_proof.data.responseBody.abiEncodedData, (PriceData)); |
| 116 | + latestVerifiedPrice = newPriceData.price; |
| 117 | +
|
| 118 | + emit PriceVerified(expectedSymbol, newPriceData.price, _proof.data.requestBody.url); |
| 119 | + } |
| 120 | + ``` |
| 121 | + |
| 122 | +- **`IICustomFeed` Implementation**: |
| 123 | + The contract provides concrete implementations for the interface methods. |
| 124 | + For example, `read()` and `getCurrentFeed()` simply return the latestVerifiedPrice and other stored data. |
| 125 | + |
| 126 | +<details> |
| 127 | + <summary> |
| 128 | + View full <code>PriceVerifierCustomFeed.sol</code> contract |
| 129 | + </summary> |
| 130 | + <CodeBlock |
| 131 | + language="solidity" |
| 132 | + title="/examples/developer-hub-solidity/PriceVerifierCustomFeed.sol" |
| 133 | + > |
| 134 | + {PriceVerifierCustomFeedSol} |
| 135 | + </CodeBlock> |
| 136 | +</details> |
| 137 | + |
| 138 | +### Offchain script: `PriceVerification.ts` |
| 139 | + |
| 140 | +The `PriceVerification.ts` script automates fetching data from CoinGecko via the FDC and submitting it to your `PriceVerifierCustomFeed` contract. |
| 141 | + |
| 142 | +It follows these sequential steps: |
| 143 | + |
| 144 | +1. **Deploy Contract:** The script first deploys the `PriceVerifierCustomFeed.sol` contract to the network. It constructs the unique `feedId` by combining the `0x21` prefix with a hash of the asset string (e.g., "BTC/USD-HIST"). |
| 145 | + |
| 146 | +2. **Prepare Attestation Request:** It constructs a request for the FDC [Web2Json](/fdc/attestation-types/web-2-json) attestation type, specifying the target API endpoint (CoinGecko), the parameters (which coin and date), and the JQ filter to extract the exact data point (the USD price) from the JSON response. |
| 147 | + |
| 148 | +3. **Submit Request to FDC:** The script sends this request to the FDC, which fetches the data, secures it through an attestation process, and makes a proof available. |
| 149 | + |
| 150 | +4. **Retrieve Proof:** After the attestation round is final, the script queries the [Data Availability (DA)](/fdc/reference/data-availability-api) layer to retrieve the finalized data and its corresponding Merkle proof. |
| 151 | + |
| 152 | +5. **Submit Proof to Custom Feed:** Finally, the script calls the `verifyPrice()` function on the deployed `PriceVerifierCustomFeed` contract, passing the retrieved proof. The contract then executes its verification logic and, if successful, updates the onchain price. |
| 153 | + |
| 154 | +<details> |
| 155 | + <summary> |
| 156 | + View full <code>PriceVerification.ts</code> script. |
| 157 | + </summary> |
| 158 | + <CodeBlock |
| 159 | + language="typescript" |
| 160 | + title="/examples/developer-hub-javascript/PriceVerification.ts" |
| 161 | + > |
| 162 | + {PriceVerificationTs} |
| 163 | + </CodeBlock> |
| 164 | +</details> |
| 165 | + |
| 166 | +## Deploy and use a Custom feed |
| 167 | + |
| 168 | +This guide walks you through using the [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) to deploy and interact with a custom price feed. |
| 169 | + |
| 170 | +### 1. Clone the hardhat starter |
| 171 | + |
| 172 | +First, clone the [`flare-hardhat-starter`](https://github.com/flare-foundation/flare-hardhat-starter) repository and navigate into the project directory: |
| 173 | + |
| 174 | +```bash |
| 175 | +git clone https://github.com/flare-foundation/flare-hardhat-starter.git |
| 176 | +cd flare-hardhat-starter |
| 177 | +``` |
| 178 | + |
| 179 | +### 2. Install dependencies |
| 180 | + |
| 181 | +Install the project dependencies using `npm` or `yarn`: |
| 182 | + |
| 183 | +```bash |
| 184 | +npm install # or yarn install |
| 185 | +``` |
| 186 | + |
| 187 | +### 3. Set up environment variables |
| 188 | + |
| 189 | +Copy the example environment file and update it with your own credentials. |
| 190 | + |
| 191 | +```bash |
| 192 | +cp .env.example .env |
| 193 | +``` |
| 194 | + |
| 195 | +You will need to provide the following: |
| 196 | + |
| 197 | +- `PRIVATE_KEY`: The private key of the account you want to use for deployment on the Coston2 testnet. This account must be funded with C2FLR tokens from the [Coston2 Faucet](https://faucet.flare.network/coston2). |
| 198 | +- `WEB2JSON_VERIFIER_URL_TESTNET`: The URL for the Web2Json Verifier service. You can leave the default value. |
| 199 | +- `VERIFIER_API_KEY_TESTNET`: An API key for the verifier service. You can get one from the [Flare Developer Portal](https://portal.flare.network/). |
| 200 | +- `COSTON2_DA_LAYER_URL`: The URL for the Data Availability Layer on Coston2. You can leave the default value. |
| 201 | + |
| 202 | +:::danger |
| 203 | + |
| 204 | +Never commit your `.env` file or share your private keys publicly. |
| 205 | + |
| 206 | +::: |
| 207 | + |
| 208 | +### 4. Run the verification script |
| 209 | + |
| 210 | +The `PriceVerification.ts` script, located in `scripts/customFeeds/`, automates the entire process. Execute it on the Coston2 Testnet: |
| 211 | + |
| 212 | +```bash |
| 213 | +npx hardhat run scripts/customFeeds/PriceVerification.ts --network coston2 |
| 214 | +``` |
| 215 | + |
| 216 | +The script will: |
| 217 | + |
| 218 | +1. Deploy the `PriceVerifierCustomFeed.sol` contract. |
| 219 | +2. Prepare an attestation request for the CoinGecko API. |
| 220 | +3. Submit the request to the Flare Data Connector (FDC). |
| 221 | +4. Wait for the attestation to be finalized. |
| 222 | +5. Retrieve the proof from the Data Availability layer. |
| 223 | +6. Submit the proof to the deployed `PriceVerifierCustomFeed` contract. |
| 224 | + |
| 225 | +### 5. Understanding the output |
| 226 | + |
| 227 | +The script will log its progress. |
| 228 | +A successful run will display the deployed contract address and the final verified price: |
| 229 | + |
| 230 | +```text |
| 231 | +Deploying PriceVerifierCustomFeed... |
| 232 | +PriceVerifierCustomFeed deployed to: 0x... (contract address) |
| 233 | +
|
| 234 | +Preparing data... |
| 235 | +// ... (attestation request details) |
| 236 | +
|
| 237 | +Submitting attestation requests... |
| 238 | +// ... (transaction details) |
| 239 | +
|
| 240 | +Waiting for round 12345 to be finalized... |
| 241 | +Round finalized. |
| 242 | +
|
| 243 | +Retrieving proofs... |
| 244 | +// ... (proof details) |
| 245 | +
|
| 246 | +Submitting proof to custom feed... |
| 247 | +Proof for BTCPrice submitted successfully... |
| 248 | +
|
| 249 | +Latest verified price: 12345 |
| 250 | +Price verification process completed successfully. |
| 251 | +``` |
| 252 | + |
| 253 | +### 6. Verify on explorer |
| 254 | + |
| 255 | +You can view your deployed contract on the [Coston2 Explorer](https://coston2-explorer.flare.network/). |
| 256 | +Use the contract address from the script's output to look it up. On the **Read Contract** tab, call the `latestVerifiedPrice()` function to confirm the price was stored onchain. |
| 257 | + |
| 258 | +## Propose a new Custom Feed |
| 259 | + |
| 260 | +If you have developed a Custom Feed that could benefit the wider ecosystem, you can propose it for official listing on the [Block-Latency Feeds](/ftso/feeds) page. |
| 261 | +To do so, submit a "New Feed Request" via an issue in the [Flare Developer Hub](https://github.com/flare-foundation/developer-hub) GitHub repository. |
| 262 | +The request should include a business justification and a link to the verified contract on a block explorer. |
| 263 | +The Flare Foundation will review the submission for eligibility. |
| 264 | + |
| 265 | +{/* prettier-ignore */} |
| 266 | +<NewGithubIssue issueType="feed_request">Propose Custom Feed</NewGithubIssue> |
| 267 | + |
| 268 | +## Conclusion |
| 269 | + |
| 270 | +You now have the tools to create FTSO Custom Feeds, bringing diverse, verifiable data from any API onto the Flare network. |
| 271 | +This capability greatly expands the possibilities for DeFi and other onchain applications. |
| 272 | +Remember, the security of your Custom Feed depends entirely on its smart contract logic and data verification process. |
| 273 | +Prioritize careful design and rigorous testing in your implementations. |
0 commit comments