Build your first FTSOv2 app
This guide is for developers who want to build an FTSOv2 application using either Foundry or Hardhat.
- Foundry
- Hardhat
In this guide, you will learn how to:
-
Create a contract to read the price of FLR/USD from FTSOv2 using flare-periphery-contracts.
-
Compile your contract using Foundry forge.
-
Deploy your contract to Flare Testnet Coston2, and interact with it using Foundry cast.
In this guide, you will learn how to:
-
Create a contract to read the price of FLR/USD from FTSOv2 using flare-periphery-contracts.
-
Compile your contract using Hardhat and run tests.
-
Deploy your contract to Flare Testnet Coston2.
Prerequisites
- Foundry
- Hardhat
Clone the template
- Foundry
- Hardhat
-
Clone the flare-foundry-starter and navigate into the project directory:
git clone https://github.com/flare-foundation/flare-foundry-starter.git
cd flare-foundry-starter -
Install the project dependencies:
forge soldeer install
-
You might need to modify
remappings.txt
so/src
paths are read correctly, e.g.:remappings.txt@openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.2.0-rc.1/
flare-periphery/=dependencies/flare-periphery-0.0.1/
forge-std/=dependencies/forge-std-1.9.5/src/
forge-std/=dependencies/forge-std-1.9.5/
surl/=dependencies/surl-0.0.0/src/
surl/=dependencies/surl-0.0.0/
-
Clone the flare-hardhat-starter and install dependencies:
git clone https://github.com/flare-foundation/flare-hardhat-starter.git
cd flare-hardhat-starter -
Install the project dependencies:
yarn install
-
Copy the environment file and set your private key:
cp .env.example .env
- Never share your private keys.
- Never put your private keys in source code.
- Never commit private keys to a Git repository.
Create and compile a contract
- Foundry
- Hardhat
-
Create a contract file
src/FtsoV2FeedConsumer.sol
, and add the following code to it:src/FtsoV2FeedConsumer.sol// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import {TestFtsoV2Interface} from "@flarenetwork/flare-periphery-contracts/coston2/TestFtsoV2Interface.sol";
import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
import {IFeeCalculator} from "@flarenetwork/flare-periphery-contracts/coston2/IFeeCalculator.sol";
contract FtsoV2Consumer {
bytes21 public constant flrUsdId =
0x01464c522f55534400000000000000000000000000; // "FLR/USD"
// Feed IDs, see https://dev.flare.network/ftso/feeds for full list
bytes21[] public feedIds = [
bytes21(0x01464c522f55534400000000000000000000000000), // FLR/USD
bytes21(0x014254432f55534400000000000000000000000000), // BTC/USD
bytes21(0x014554482f55534400000000000000000000000000) // ETH/USD
];
function getFlrUsdPrice() external view returns (uint256, int8, uint64) {
/* THIS IS A TEST METHOD, in production use: ftsoV2 = ContractRegistry.getFtsoV2(); */
TestFtsoV2Interface ftsoV2 = ContractRegistry.getTestFtsoV2();
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedById(flrUsdId);
}
function getFlrUsdPriceWei() external view returns (uint256, uint64) {
/* THIS IS A TEST METHOD, in production use: ftsoV2 = ContractRegistry.getFtsoV2(); */
TestFtsoV2Interface ftsoV2 = ContractRegistry.getTestFtsoV2();
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedByIdInWei(flrUsdId);
}
function getFtsoV2CurrentFeedValues()
external
view
returns (
uint256[] memory _feedValues,
int8[] memory _decimals,
uint64 _timestamp
)
{
/* THIS IS A TEST METHOD, in production use: ftsoV2 = ContractRegistry.getFtsoV2(); */
TestFtsoV2Interface ftsoV2 = ContractRegistry.getTestFtsoV2();
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedsById(feedIds);
}
} -
Set EVM version to
london
infoundry.toml
:foundry.toml[profile.default]
...
evm_version = "london" -
To ensure everything is set up correctly, compile the contract by running:
forge build
The output should indicate that the compilation was successful.
[⠊] Compiling...
[⠃] Compiling 27 files with Solc 0.8.27
[⠊] Solc 0.8.27 finished in 853.78ms
Compiler run successful!
-
Create a contract file
contracts/FTSOV2Consumer.sol
:contracts/FTSOV2Consumer.sol// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import {TestFtsoV2Interface} from "@flarenetwork/flare-periphery-contracts/coston2/TestFtsoV2Interface.sol";
import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
import {IFeeCalculator} from "@flarenetwork/flare-periphery-contracts/coston2/IFeeCalculator.sol";
contract FtsoV2Consumer {
bytes21 public constant flrUsdId =
0x01464c522f55534400000000000000000000000000; // "FLR/USD"
// Feed IDs, see https://dev.flare.network/ftso/feeds for full list
bytes21[] public feedIds = [
bytes21(0x01464c522f55534400000000000000000000000000), // FLR/USD
bytes21(0x014254432f55534400000000000000000000000000), // BTC/USD
bytes21(0x014554482f55534400000000000000000000000000) // ETH/USD
];
function getFlrUsdPrice() external view returns (uint256, int8, uint64) {
/* THIS IS A TEST METHOD, in production use: ftsoV2 = ContractRegistry.getFtsoV2(); */
TestFtsoV2Interface ftsoV2 = ContractRegistry.getTestFtsoV2();
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedById(flrUsdId);
}
function getFlrUsdPriceWei() external view returns (uint256, uint64) {
/* THIS IS A TEST METHOD, in production use: ftsoV2 = ContractRegistry.getFtsoV2(); */
TestFtsoV2Interface ftsoV2 = ContractRegistry.getTestFtsoV2();
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedByIdInWei(flrUsdId);
}
function getFtsoV2CurrentFeedValues()
external
view
returns (
uint256[] memory _feedValues,
int8[] memory _decimals,
uint64 _timestamp
)
{
/* THIS IS A TEST METHOD, in production use: ftsoV2 = ContractRegistry.getFtsoV2(); */
TestFtsoV2Interface ftsoV2 = ContractRegistry.getTestFtsoV2();
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedsById(feedIds);
}
} -
Set EVM version to
london
inhardhat.config.ts
:module.exports = {
solidity: {
version: "0.8.27",
settings: {
evmVersion: "london",
optimizer: {
enabled: true,
runs: 200,
},
},
},
}; -
Compile the contract:
npx hardhat compile
Write tests
- Foundry
- Hardhat
-
Create a test file
test/FtsoV2FeedConsumer_foundry.t.sol
, and add the following code:test/FtsoV2FeedConsumer_foundry.t.sol// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0 <0.9.0;
import "forge-std/Test.sol";
import {FtsoV2FeedConsumer} from "../src/FtsoV2FeedConsumer.sol";
contract MockFtsoV2 {
function getFeedById(
bytes21 /*_feedId*/
) external payable returns (uint256, int8, uint64) {
return (150000, 7, uint64(block.timestamp));
}
}
contract MockFeeCalculator {
function calculateFeeByIds(
bytes21[] memory /*_feedIds*/
) external pure returns (uint256) {
return 0;
}
}
contract FtsoV2FeedConsumerTest is Test {
FtsoV2FeedConsumer public feedConsumer;
MockFtsoV2 public mockFtsoV2;
MockFeeCalculator public mockFeeCalc;
bytes21 constant flrUsdId =
bytes21(0x01464c522f55534400000000000000000000000000);
function setUp() public {
mockFtsoV2 = new MockFtsoV2();
mockFeeCalc = new MockFeeCalculator();
feedConsumer = new FtsoV2FeedConsumer(
address(mockFtsoV2),
address(mockFeeCalc),
flrUsdId
);
}
function testCheckFees() public {
assertEq(feedConsumer.checkFees(), 0, "Feed value mismatch");
}
function testGetFlrUsdPrice() public {
(uint256 feedValue, int8 decimals, uint64 timestamp) = feedConsumer
.getFlrUsdPrice{value: 0}();
assertEq(feedValue, 150000, "Feed value mismatch");
assertEq(decimals, 7, "Decimals mismatch");
assertApproxEqAbs(
timestamp,
uint64(block.timestamp),
2,
"Timestamp mismatch"
);
}
} -
Run the tests:
forge test
You should see a successful test result like this:
[⠊] Compiling...
[⠘] Compiling 27 files with Solc 0.8.27
[⠃] Solc 0.8.27 finished in 797.51ms
Compiler run successful!
Ran 2 tests for test/FtsoV2FeedConsumer_foundry.t.sol:FtsoV2FeedConsumerTestFoundry
[PASS] testCheckFees() (gas: 21085)
[PASS] testGetFlrUsdPrice() (gas: 25610)
Logs:
msg.value matches fee
feedValue 150000
decimals 7
timestamp 1
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.72ms (2.91ms CPU time)
Ran 1 test suite in 122.65ms (7.72ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)
-
Create a test directory and file
test/FtsoV2Consumer.test.ts
:test/FtsoV2ConsumerHardhat.tsimport { expect } from "chai";
import { ethers } from "hardhat";
import { FtsoV2Consumer } from "../typechain-types"; // Adjust the path based on your typechain output directory
describe("FtsoV2Consumer", function () {
let ftsoV2Consumer: FtsoV2Consumer;
// Deploy a new instance of the contract before each test
beforeEach(async function () {
const FtsoV2ConsumerFactory =
await ethers.getContractFactory("FtsoV2Consumer");
ftsoV2Consumer = await FtsoV2ConsumerFactory.deploy();
await ftsoV2Consumer.waitForDeployment();
// console.log("FtsoV2Consumer deployed to:", await ftsoV2Consumer.getAddress()); // Optional: log address
});
it("Should return the FLR/USD price, decimals, and timestamp", async function () {
const [price, decimals, timestamp] = await ftsoV2Consumer.getFlrUsdPrice();
// We can't know the exact price, but we can check the types and basic validity
expect(price).to.be.a("bigint");
expect(price).to.be.gt(0); // Price should be greater than 0
expect(decimals).to.be.a("number");
// Typical FTSO decimals are often 5 for USD pairs, but let's be flexible
expect(decimals).to.be.within(-128, 127); // Check it's a valid int8
expect(timestamp).to.be.a("bigint");
expect(timestamp).to.be.gt(0); // Timestamp should be positive
// Check if the timestamp is reasonably recent (e.g., within the last hour)
const currentTimestamp = Math.floor(Date.now() / 1000);
expect(Number(timestamp)).to.be.closeTo(currentTimestamp, 3600); // within 1 hour
});
it("Should return the FLR/USD price in Wei and timestamp", async function () {
const [priceWei, timestamp] = await ftsoV2Consumer.getFlrUsdPriceWei();
expect(priceWei).to.be.a("bigint");
expect(priceWei).to.be.gt(0); // Price in Wei should be greater than 0
expect(timestamp).to.be.a("bigint");
expect(timestamp).to.be.gt(0); // Timestamp should be positive
const currentTimestamp = Math.floor(Date.now() / 1000);
expect(Number(timestamp)).to.be.closeTo(currentTimestamp, 3600); // within 1 hour
});
it("Should return current feed values for multiple feeds", async function () {
const [feedValues, decimals, timestamp] =
await ftsoV2Consumer.getFtsoV2CurrentFeedValues();
// Get the expected number of feeds from the contract's public variable
const expectedFeedCount = (await ftsoV2Consumer.feedIds()).length;
expect(feedValues).to.be.an("array");
expect(feedValues).to.have.lengthOf(expectedFeedCount);
feedValues.forEach((value) => {
expect(value).to.be.a("bigint");
expect(value).to.be.gt(0);
});
expect(decimals).to.be.an("array");
expect(decimals).to.have.lengthOf(expectedFeedCount);
decimals.forEach((dec) => {
expect(dec).to.be.a("number");
expect(dec).to.be.within(-128, 127); // Check it's a valid int8
});
expect(timestamp).to.be.a("bigint");
expect(timestamp).to.be.gt(0); // Timestamp should be positive
const currentTimestamp = Math.floor(Date.now() / 1000);
expect(Number(timestamp)).to.be.closeTo(currentTimestamp, 3600); // within 1 hour
});
it("Should have the correct constant flrUsdId", async function () {
const expectedId = "0x01464c522f55534400000000000000000000000000"; // "FLR/USD"
const actualId = await ftsoV2Consumer.flrUsdId();
expect(actualId).to.equal(expectedId);
});
it("Should have the correct initial feedIds array", async function () {
const expectedIds = [
"0x01464c522f55534400000000000000000000000000", // FLR/USD
"0x014254432f55534400000000000000000000000000", // BTC/USD
"0x014554482f55534400000000000000000000000000", // ETH/USD
];
const actualIds = await ftsoV2Consumer.feedIds();
expect(actualIds).to.deep.equal(expectedIds);
});
}); -
Run the tests:
npx hardhat test
Deploy and interact with contract
- Foundry
- Hardhat
-
Generate a new wallet using the cast:
cast wallet new
The output will look something like:
Successfully created new keypair.
Address: 0x3f6BdD26f2AE4e77AcDfA1FA24B2774ed93984B4
Private key: 0x84cf77b009a92777f75b49864e4166ddcaf8f3f5f119a19b226ab362a0cf7bf5 -
Store your wallet details and the RPC URL as environment variables:
danger- Never share your private keys.
- Never put your private keys in source code.
- Never commit private keys to a Git repository.
export ACCOUNT=<address above>
export ACCOUNT_PRIVATE_KEY=<private key above>
export RPC_URL="https://coston2-api.flare.network/ext/C/rpc" -
Use the Coston2 Faucet to get some testnet C2FLR tokens. You can verify that the 100 C2FLR has arrived in your wallet:
cast balance $ACCOUNT -r $RPC_URL -e
-
The final step before deploying is to set the constructor arguments with the address of
FtsoV2
andFeeCalculator
on Flare Testnet Coston2 and the feed ID of FLR/USD:export FTSOV2_COSTON2=0x3d893C53D9e8056135C26C8c638B76C8b60Df726
export FEECALCULATOR_COSTON2=0x88A9315f96c9b5518BBeC58dC6a914e13fAb13e2
export FLRUSD_FEED_ID=0x01464c522f55534400000000000000000000000000You can now deploy the contract:
forge create src/FtsoV2FeedConsumer.sol:FtsoV2FeedConsumer --private-key $ACCOUNT_PRIVATE_KEY --rpc-url $RPC_URL --constructor-args $FTSOV2_COSTON2 $FEECALCULATOR_COSTON2 $FLRUSD_FEED_ID
If the deployment is successful, the output will display the contract address, save that for later use:
[⠊] Compiling...
[⠘] Compiling 24 files with Solc 0.8.27
[⠃] Solc 0.8.27 finished in 733.41ms
Compiler run successful!
Deployer: 0x3f6BdD26f2AE4e77AcDfA1FA24B2774ed93984B4
Deployed to: 0x80Ee4091348d9fA4B4A84Eb525c25049EbDa6152
Transaction hash: 0x38604a643695959dd9fa5547d95610fb0b7393c7e8358079f47ed4bdb53c9a8fexport DEPLOYMENT_ADDRESS=<deployed to address above>
-
Use
cast
to interact with the contract, note that this command uses the environment variables defined in the sections above.:cast send --private-key $ACCOUNT_PRIVATE_KEY --rpc-url $RPC_URL -j --value 0 $DEPLOYMENT_ADDRESS "getFlrUsdPrice()"
Expected output of the command above.
{
"status": "0x1",
"cumulativeGasUsed": "0x1cbab",
"logs": [
{
"address": "0x1000000000000000000000000000000000000002",
"topics": [
"0xe7aa66356adbd5e839ef210626f6d8f6f72109c17fadf4c4f9ca82b315ae79b4"
],
"data": "0x00000000000000000000000098b8e9b5830f04fe3b8d56a2f8455e337037ba280000000000000000000000000000000000000000000000000000000000004231",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"logIndex": "0x0",
"removed": false
},
{
"address": "0x1000000000000000000000000000000000000002",
"topics": [
"0xe7aa66356adbd5e839ef210626f6d8f6f72109c17fadf4c4f9ca82b315ae79b4"
],
"data": "0x0000000000000000000000004f52e61907b0ed9f26b88f16b2510a4ca524d6d00000000000000000000000000000000000000000000000000000000000003099",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"logIndex": "0x1",
"removed": false
},
{
"address": "0x1000000000000000000000000000000000000002",
"topics": [
"0xe7aa66356adbd5e839ef210626f6d8f6f72109c17fadf4c4f9ca82b315ae79b4"
],
"data": "0x000000000000000000000000d2a1bb23eb350814a30dd6f9de78bb2c8fdd9f1d0000000000000000000000000000000000000000000000000000000000003b68",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"logIndex": "0x2",
"removed": false
},
{
"address": "0x1000000000000000000000000000000000000002",
"topics": [
"0xe7aa66356adbd5e839ef210626f6d8f6f72109c17fadf4c4f9ca82b315ae79b4"
],
"data": "0x0000000000000000000000006892bdbbb14e1c9bd46bf31e7bac94d038fc82a6000000000000000000000000000000000000000000000000000000000000422d",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"logIndex": "0x3",
"removed": false
},
{
"address": "0x1000000000000000000000000000000000000002",
"topics": [
"0xe7aa66356adbd5e839ef210626f6d8f6f72109c17fadf4c4f9ca82b315ae79b4"
],
"data": "0x000000000000000000000000bd33bdff04c357f7fc019e72d0504c24cf4aa0100000000000000000000000000000000000000000000000000000000000008f11",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"logIndex": "0x4",
"removed": false
},
{
"address": "0x1000000000000000000000000000000000000002",
"topics": [
"0xe7aa66356adbd5e839ef210626f6d8f6f72109c17fadf4c4f9ca82b315ae79b4"
],
"data": "0x000000000000000000000000a90db6d10f856799b10ef2a77ebcbf460ac71e520000000000000000000000000000000000000000000000000000000000004e9c",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"logIndex": "0x5",
"removed": false
},
{
"address": "0x1000000000000000000000000000000000000002",
"topics": [
"0xe7aa66356adbd5e839ef210626f6d8f6f72109c17fadf4c4f9ca82b315ae79b4"
],
"data": "0x0000000000000000000000000b162ca3acf3482d3357972e12d794434085d839000000000000000000000000000000000000000000000000000000000000e5a6",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"logIndex": "0x6",
"removed": false
}
],
"logsBloom": "0x000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000004000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"type": "0x2",
"transactionHash": "0x3fdc9cf00456a7878476877b6f8ae5c994dd3c224ca792f965f718340fd98402",
"transactionIndex": "0x0",
"blockHash": "0x94f50404f8205caff551ef2b08d20afc4c080bd7b8231cd3941f1a7a6b1b80dd",
"blockNumber": "0xb2b972",
"gasUsed": "0x1cbab",
"effectiveGasPrice": "0x6fc23ac00",
"from": "0x3f6bdd26f2ae4e77acdfa1fa24b2774ed93984b4",
"to": "0x80ee4091348d9fa4b4a84eb525c25049ebda6152",
"contractAddress": null
}
You can see the transaction using the Coston2 Explorer by searching for its transactionHash
.
Congratulations! You've built your first FTSOv2 app using Foundry.
-
Create a deployment script
scripts/deployFTSOConsumer.ts
:scripts/deployFTSOConsumer.tsimport { ethers, run } from "hardhat";
import { FtsoV2ConsumerContract } from "../typechain-types";
const FtsoV2Consumer = artifacts.require("FtsoV2Consumer");
async function verifyContract(address: string) {
console.log("\nVerifying contract...");
try {
await run("verify:verify", { address });
console.log("Contract verified successfully");
} catch (error) {
console.error("Error verifying contract:", error);
}
}
async function callGetFlrUsdPrice(ftsoV2Consumer: FtsoV2ConsumerContract) {
console.log("\nCalling getFlrUsdPrice...");
try {
const result = await ftsoV2Consumer.getFlrUsdPrice();
console.log("FLR/USD Price Data:");
console.log(" Price:", result[0].toString());
console.log(" Decimals:", result[1].toString());
console.log(
" Timestamp:",
new Date(result[2].toNumber() * 1000).toISOString(),
);
} catch (error) {
console.error("Error calling getFlrUsdPrice:", error);
}
}
async function callGetFlrUsdPriceWei(ftsoV2Consumer: FtsoV2ConsumerContract) {
console.log("\nCalling getFlrUsdPriceWei...");
try {
const resultWei = await ftsoV2Consumer.getFlrUsdPriceWei();
console.log("FLR/USD Price Data (Wei):");
console.log(" Price (Wei):", resultWei[0].toString());
console.log(
" Timestamp:",
new Date(resultWei[1].toNumber() * 1000).toISOString(),
);
} catch (error) {
console.error("Error calling getFlrUsdPriceWei:", error);
}
}
async function callFtsoV2CurrentFeedValues(
ftsoV2Consumer: FtsoV2ConsumerContract,
) {
console.log("\nCalling getFtsoV2CurrentFeedValues...");
try {
const feedValuesResult = await ftsoV2Consumer.getFtsoV2CurrentFeedValues();
console.log("Current Feed Values (FLR/USD, BTC/USD, ETH/USD):");
const feedIds = ["FLR/USD", "BTC/USD", "ETH/USD"]; // Match order in contract
for (let i = 0; i < feedValuesResult[0].length; i++) {
console.log(` Feed ${feedIds[i]}:`);
console.log(` Price: ${feedValuesResult[0][i].toString()}`);
console.log(` Decimals: ${feedValuesResult[1][i].toString()}`);
}
console.log(
` Timestamp: ${new Date(feedValuesResult[2].toNumber() * 1000).toISOString()}`,
);
} catch (error) {
console.error("Error calling getFtsoV2CurrentFeedValues:", error);
}
}
async function callContractFunctions(ftsoV2Consumer: FtsoV2ConsumerContract) {
await callGetFlrUsdPrice(ftsoV2Consumer);
await callGetFlrUsdPriceWei(ftsoV2Consumer);
await callFtsoV2CurrentFeedValues(ftsoV2Consumer);
}
// --- Main Deployment Script ---
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
// Deploy FtsoV2Consumer
const ftsoV2Consumer: FtsoV2ConsumerContract = await FtsoV2Consumer.new(
"0x01464c522f55534400000000000000000000000000", // Deploying contract with FLR Feed (see more feeds here: https://dev.flare.network/ftso/feeds)
);
await ftsoV2Consumer.deployed();
console.log("FtsoV2Consumer deployed to:", ftsoV2Consumer.address);
// Verify the contract
await verifyContract(ftsoV2Consumer.address);
// Call the contract functions
await callContractFunctions(ftsoV2Consumer);
console.log("\nDeployment and calling script finished.");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error("Unhandled error in main execution:", error);
process.exit(1);
}); -
Deploy to Flare Testnet Coston2:
npx hardhat run scripts/deployFTSOConsumer.ts --network coston2
-
Interact with the contract:
Copy and paste the deployed contract address into the Coston2 explorer to view and interact with the contract.
Congratulations! You've built your first FTSOv2 app using Hardhat.
Learn how to read feeds offchain using JavaScript, Python, Rust and Go, or learn how to change quote feed with an onchain Solidity contract.