-
Notifications
You must be signed in to change notification settings - Fork 45
docs(ftso): ftso adapters #952
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
60695c4
docs(ftso): FTSO adapters
0xreflexivity 17eac1e
docs(ftso): improve wording
0xreflexivity 71c7668
docs(ftso): fix link
0xreflexivity ed77e2b
chore(ftso): clean up
0xreflexivity 2d4d562
fix(docs): remove tabs to preserve sidebar links
0xreflexivity 6337f45
fix(docs): simplify adapter headings
dineshpinto 0aae073
fix(docs): add tags to adapters
dineshpinto ab1c54d
fix(docs): typo in adapters
dineshpinto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| --- | ||
| slug: adapters | ||
| title: Migrate an app to FTSO | ||
| tags: [intermediate, ftso, solidity, javascript] | ||
| description: Migrate your dApp from other oracles to FTSO. | ||
| keywords: | ||
| [ | ||
| solidity, | ||
| reference, | ||
| ftso, | ||
| flare-time-series-oracle, | ||
| flare-network, | ||
| smart-contracts, | ||
| chainlink, | ||
| pyth, | ||
| api3, | ||
| band, | ||
| chronicle, | ||
| ] | ||
| --- | ||
|
|
||
| import CodeBlock from "@theme/CodeBlock"; | ||
| import ChainlinkExample from "!!raw-loader!/examples/developer-hub-solidity/ChainlinkExample.sol"; | ||
| import chainlinkExample from "!!raw-loader!/examples/developer-hub-javascript/chainlinkExample.ts"; | ||
| import PythExample from "!!raw-loader!/examples/developer-hub-solidity/PythExample.sol"; | ||
| import pythExample from "!!raw-loader!/examples/developer-hub-javascript/pythExample.ts"; | ||
| import Api3Example from "!!raw-loader!/examples/developer-hub-solidity/Api3Example.sol"; | ||
| import api3Example from "!!raw-loader!/examples/developer-hub-javascript/api3Example.ts"; | ||
| import BandExample from "!!raw-loader!/examples/developer-hub-solidity/BandExample.sol"; | ||
| import bandExample from "!!raw-loader!/examples/developer-hub-javascript/bandExample.ts"; | ||
| import ChronicleExample from "!!raw-loader!/examples/developer-hub-solidity/ChronicleExample.sol"; | ||
| import chronicleExample from "!!raw-loader!/examples/developer-hub-javascript/chronicleExample.ts"; | ||
|
|
||
| FTSO Adapters, provided by the `@flarenetwork/ftso-adapters` library, allow decentralized applications (dApps) built for other popular oracle interfaces to integrate with Flare's FTSO with minimal code changes. | ||
| The library provides adapters for Pyth, Chainlink, API3, Band Protocol, and Chronicle. | ||
| These adapters act as a compatibility layer, translating the FTSO's data structure into the format expected by each respective oracle's interface. | ||
|
|
||
| This enables a seamless migration path for projects looking to leverage the speed, decentralization, and cost-effectiveness of Flare's native oracle. | ||
| This guide focuses on the specific code modifications required to migrate your existing dApp. | ||
|
|
||
| All code examples can be found in our [hardhat-starter-kit](https://github.com/flare-foundation/flare-hardhat-starter/tree/master/contracts/adapters). | ||
|
|
||
| ## Key code changes | ||
|
|
||
| Migrating to a Flare FTSO adapter requires a different approach to handling oracle data. Instead of your contract calling an external oracle, it will now manage price data internally by using an adapter library. | ||
| This process involves two main changes: modifying your smart contract and setting up a new offchain keeper process. | ||
|
|
||
| ### 1. Onchain: Use the adapter library | ||
|
|
||
| The main changes happen within your smart contract. You will modify it to store, update, and serve the FTSO price data itself. | ||
|
|
||
| - **State Variables**: Instead of storing an address to an external oracle, you add state variables to your contract to manage the FTSO feed and cache the price data. | ||
| - **Before**: `AggregatorV3Interface internal dataFeed;` | ||
| - **After**: `bytes21 public immutable ftsoFeedId; FtsoChainlinkAdapterLibrary.Round private _latestPriceData;` | ||
|
|
||
| - **Constructor**: Your constructor no longer needs an oracle's address. Instead, it takes FTSO-specific information, such as the `ftsoFeedId` and any other parameters the adapter needs (like `chainlinkDecimals`). | ||
| - **Before**: `constructor(address _dataFeedAddress) { dataFeed = AggregatorV3Interface(_dataFeedAddress); }` | ||
| - **After**: `constructor(bytes21 _ftsoFeedId, uint8 _chainlinkDecimals) { ftsoFeedId = _ftsoFeedId; chainlinkDecimals = _chainlinkDecimals; }` | ||
|
|
||
| - **Implement `refresh()`**: You must add a public `refresh()` function. This function's only job is to call the adapter library's `refresh` logic, which updates your contract's state variables with the latest FTSO price. | ||
|
|
||
| - **Implement the Oracle Interface**: You then add the standard `view` function for the oracle you are migrating from (e.g., `latestRoundData()` for Chainlink). | ||
| This function calls the corresponding logic from the adapter library, reading directly from your contract's cached state. | ||
|
|
||
| - **No Change to Core Logic**: Your dApp's internal logic that uses the price data remains unchanged. | ||
| It continues to call the same standard oracle functions as before (e.g., `latestRoundData()`), but now it's calling a function implemented directly within your own contract. | ||
|
|
||
| ### 2. Offchain: Set up a keeper bot | ||
|
|
||
| Since your contract now manages its own price updates, you need an external process to trigger them. | ||
|
|
||
| - **Create a Keeper Script**: This is a simple script that connects to the network and periodically calls the public `refresh()` function on your deployed contract. | ||
| - **Run the Keeper**: This script ensures the price cached in your contract stays fresh. It replaces the need to rely on the oracle provider's keepers, giving you direct control over how often your prices are updated and how much you spend on gas. | ||
|
|
||
| ## FtsoChainlinkAdapter | ||
|
|
||
| The `FtsoChainlinkAdapter` implements Chainlink's `AggregatorV3Interface`. The example is an `AssetVault` contract that uses the FTSO price to value collateral for borrowing and lending. | ||
|
|
||
| ### `ChainlinkExample.sol` | ||
|
|
||
| <CodeBlock language="solidity" title="/contracts/adapters/ChainlinkExample.sol"> | ||
| {ChainlinkExample} | ||
| </CodeBlock> | ||
|
|
||
| ### `chainlinkExample.ts` | ||
|
|
||
| <CodeBlock language="typescript" title="/scripts/adapters/chainlinkExample.ts"> | ||
| {chainlinkExample} | ||
| </CodeBlock> | ||
|
|
||
| ## FtsoPythAdapter | ||
|
|
||
| The `FtsoPythAdapter` implements Pyth's `IPyth` interface. The example is a `PythNftMinter` contract that dynamically calculates a $1 minting fee based on the live FTSO price. | ||
|
|
||
| ### `PythExample.sol` | ||
|
|
||
| <CodeBlock language="solidity" title="/contracts/adapters/PythExample.sol"> | ||
| {PythExample} | ||
| </CodeBlock> | ||
|
|
||
| ### `pythExample.ts` | ||
|
|
||
| <CodeBlock language="typescript" title="/scripts/adapters/pythExample.ts"> | ||
| {pythExample} | ||
| </CodeBlock> | ||
|
|
||
| ## FtsoApi3Adapter | ||
|
|
||
| The `FtsoApi3Adapter` implements the `IApi3ReaderProxy` interface. The example is a `PriceGuesser` prediction market that uses the FTSO price to settle bets. | ||
|
|
||
| ### `Api3Example.sol` | ||
|
|
||
| <CodeBlock language="solidity" title="/contracts/adapters/Api3Example.sol"> | ||
| {Api3Example} | ||
| </CodeBlock> | ||
|
|
||
| ### `api3Example.ts` | ||
|
|
||
| <CodeBlock language="typescript" title="/scripts/adapters/api3Example.ts"> | ||
| {api3Example} | ||
| </CodeBlock> | ||
|
|
||
| ## FtsoBandAdapter | ||
|
|
||
| The `FtsoBandAdapter` implements Band Protocol's `IStdReference` interface. The example is a `PriceTriggeredSafe` that locks withdrawals during high market volatility, detected by checking a basket of FTSO prices. | ||
|
|
||
| ### `BandExample.sol` | ||
|
|
||
| <CodeBlock language="solidity" title="/contracts/adapters/BandExample.sol"> | ||
| {BandExample} | ||
| </CodeBlock> | ||
|
|
||
| ### `bandExample.ts` | ||
|
|
||
| <CodeBlock language="typescript" title="/scripts/adapters/bandExample.ts"> | ||
| {bandExample} | ||
| </CodeBlock> | ||
|
|
||
| ## FtsoChronicleAdapter | ||
|
|
||
| The `FtsoChronicleAdapter` implements the `IChronicle` interface. The example is a `DynamicNftMinter` that mints NFTs of different tiers based on the live FTSO asset price. | ||
|
|
||
| ### `ChronicleExample.sol` | ||
|
|
||
| <CodeBlock language="solidity" title="/contracts/adapters/ChronicleExample.sol"> | ||
| {ChronicleExample} | ||
| </CodeBlock> | ||
|
|
||
| ### `chronicleExample.ts` | ||
|
|
||
| <CodeBlock language="typescript" title="/scripts/adapters/chronicleExample.ts"> | ||
| {chronicleExample} | ||
| </CodeBlock> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| import { artifacts, run, web3 } from "hardhat"; | ||
| import { PriceGuesserInstance } from "../../typechain-types"; | ||
|
|
||
| // --- Configuration --- | ||
| const PriceGuesser: PriceGuesserInstance = artifacts.require("PriceGuesser"); | ||
| const FTSO_FEED_ID = "0x01464c522f55534400000000000000000000000000"; | ||
| const DESCRIPTION = "FTSOv2 FLR/USD adapted for API3"; | ||
| const MAX_AGE_SECONDS = 3600; | ||
| const STRIKE_PRICE_USD = 0.025; | ||
| const ROUND_DURATION_SECONDS = 300; | ||
| const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | ||
|
|
||
| async function deployContracts(): Promise<{ guesser: PriceGuesserInstance }> { | ||
| const strikePriceWei = BigInt(STRIKE_PRICE_USD * 1e18); | ||
| const guesserArgs: (string | number)[] = [ | ||
| FTSO_FEED_ID, | ||
| DESCRIPTION, | ||
| MAX_AGE_SECONDS, | ||
| strikePriceWei.toString(), | ||
| ROUND_DURATION_SECONDS, | ||
| ]; | ||
| console.log("\nDeploying integrated PriceGuesser contract with arguments:"); | ||
| console.log(` - FTSO Feed ID: ${guesserArgs[0]}`); | ||
| console.log(` - Description: ${guesserArgs[1]}`); | ||
| console.log(` - Max Age (seconds): ${guesserArgs[2]}`); | ||
| console.log(` - Strike Price: ${STRIKE_PRICE_USD} (${guesserArgs[3]} wei)`); | ||
| console.log(` - Round Duration: ${guesserArgs[4]} seconds`); | ||
| const guesser = await PriceGuesser.new( | ||
| ...(guesserArgs as [string, string, number, string, number]), | ||
| ); | ||
| console.log("\n✅ PriceGuesser deployed to:", guesser.address); | ||
|
|
||
| try { | ||
| console.log("\nVerifying PriceGuesser on block explorer..."); | ||
| await run("verify:verify", { | ||
| address: guesser.address, | ||
| constructorArguments: guesserArgs, | ||
| }); | ||
| console.log("PriceGuesser verification successful."); | ||
| } catch (e: unknown) { | ||
| if (e instanceof Error) { | ||
| console.error("PriceGuesser verification failed:", e.message); | ||
| } else { | ||
| console.error("An unknown error occurred during verification:", e); | ||
| } | ||
| } | ||
|
|
||
| return { guesser }; | ||
| } | ||
|
|
||
| async function interactWithMarket(guesser: PriceGuesserInstance) { | ||
| const accounts = await web3.eth.getAccounts(); | ||
| const deployer = accounts[0]; | ||
| const bettorAbove = accounts.length > 1 ? accounts[1] : deployer; | ||
| const bettorBelow = accounts.length > 2 ? accounts[2] : deployer; | ||
| const betAmountAbove = 10n * 10n ** 18n; | ||
| const betAmountBelow = 20n * 10n ** 18n; | ||
|
|
||
| console.log(`\n--- Simulating Prediction Market ---`); | ||
| console.log(` - Deployer/Settler: ${deployer}`); | ||
| console.log(` - Bettor "Above": ${bettorAbove}`); | ||
| console.log(` - Bettor "Below": ${bettorBelow}`); | ||
|
|
||
| console.log("\nStep 1: Bettors are placing their bets..."); | ||
| await guesser.betAbove({ | ||
| from: bettorAbove, | ||
| value: betAmountAbove.toString(), | ||
| }); | ||
| console.log( | ||
| ` - Bettor "Above" placed ${web3.utils.fromWei(betAmountAbove.toString())} tokens.`, | ||
| ); | ||
| await guesser.betBelow({ | ||
| from: bettorBelow, | ||
| value: betAmountBelow.toString(), | ||
| }); | ||
| console.log( | ||
| ` - Bettor "Below" placed ${web3.utils.fromWei(betAmountBelow.toString())} tokens.`, | ||
| ); | ||
|
|
||
| console.log( | ||
| `\nStep 2: Betting round is live. Waiting ${ROUND_DURATION_SECONDS} seconds for it to expire...`, | ||
| ); | ||
| await wait(ROUND_DURATION_SECONDS * 1000); | ||
| console.log(" - The betting round has now expired."); | ||
|
|
||
| console.log( | ||
| "\nStep 3: Refreshing the FTSO price on the contract post-expiry...", | ||
| ); | ||
| await guesser.refresh({ from: deployer }); | ||
| console.log(" - Price has been updated on the PriceGuesser contract."); | ||
|
|
||
| console.log("\nStep 4: Settling the prediction market..."); | ||
| const settleTx = await guesser.settle({ from: deployer }); | ||
| const settledEvent = settleTx.logs.find((e) => e.event === "MarketSettled"); | ||
| const finalPrice = BigInt(settledEvent.args.finalPrice.toString()); | ||
| const outcome = Number(settledEvent.args.outcome); | ||
| const finalPriceFormatted = Number(finalPrice / 10n ** 14n) / 10000; | ||
| const outcomeString = outcome === 1 ? "ABOVE" : "BELOW"; | ||
| console.log( | ||
| `✅ Market settled! Final Price: ${finalPriceFormatted.toFixed(4)}`, | ||
| ); | ||
| console.log(`✅ Outcome: The price was ${outcomeString} the strike price.`); | ||
|
|
||
| console.log("\nStep 5: Distributing winnings..."); | ||
| const [winner, loser] = | ||
| outcome === 1 ? [bettorAbove, bettorBelow] : [bettorBelow, bettorAbove]; | ||
| const prizePool = outcome === 1 ? betAmountBelow : betAmountAbove; | ||
| const winnerBet = outcome === 1 ? betAmountAbove : betAmountBelow; | ||
|
|
||
| if (prizePool > 0n || winnerBet > 0n) { | ||
| console.log(` - Attempting to claim for WINNER ("${outcomeString}")`); | ||
| await guesser.claimWinnings({ from: winner }); | ||
| const totalWinnings = winnerBet + prizePool; | ||
| console.log( | ||
| ` - WINNER claimed their prize of ${web3.utils.fromWei(totalWinnings.toString())} tokens.`, | ||
| ); | ||
| } else { | ||
| console.log(" - WINNER's pool won, but no bets were placed to claim."); | ||
| } | ||
|
|
||
| if (winner !== loser) { | ||
| try { | ||
| await guesser.claimWinnings({ from: loser }); | ||
| } catch (error: unknown) { | ||
| if (error instanceof Error && error.message.includes("NothingToClaim")) { | ||
| console.log( | ||
| " - LOSER correctly failed to claim winnings as expected.", | ||
| ); | ||
| } else if (error instanceof Error) { | ||
| console.error( | ||
| " - An unexpected error occurred for the loser:", | ||
| error.message, | ||
| ); | ||
| } else { | ||
| console.error(" - An unknown error occurred for the loser:", error); | ||
| } | ||
| } | ||
| } else { | ||
| console.log( | ||
| " - Skipping loser claim attempt as winner and loser are the same account.", | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| async function main() { | ||
| console.log("🚀 Starting Prediction Market Management Script 🚀"); | ||
| const { guesser } = await deployContracts(); | ||
| await interactWithMarket(guesser); | ||
| console.log("\n🎉 Script finished successfully! 🎉"); | ||
| } | ||
|
|
||
| void main() | ||
| .then(() => process.exit(0)) | ||
| .catch((error) => { | ||
| console.error(error); | ||
| process.exit(1); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.